From 252e88ee93cbeee3d50b41af6b5be683b7a07f99 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Thu, 3 Sep 2026 15:57:17 -0300 Subject: [PATCH 1/9] add new odata-core-apache-httpclient5 module, migrate FluentHelpers to hc5 APIs, remove CSRF token control methods now handled by the hc5 interceptor --- .../odata-core-apache-httpclient5/bin/pom.xml | 126 +++++ .../odata/adapter/ODataNumberSerializer.class | Bin 0 -> 3244 bytes ...CollectionValuedFluentHelperFunction.class | Bin 0 -> 1566 bytes .../datamodel/odata/helper/EntityField.class | Bin 0 -> 12029 bytes .../odata/helper/EntityFieldAll.class | Bin 0 -> 4713 bytes .../datamodel/odata/helper/EntityLink.class | Bin 0 -> 14867 bytes .../odata/helper/EntitySelectable.class | Bin 0 -> 1820 bytes .../odata/helper/ExpressionFluentHelper.class | Bin 0 -> 4469 bytes .../odata/helper/FluentHelperBasic.class | Bin 0 -> 10440 bytes .../odata/helper/FluentHelperByKey.class | Bin 0 -> 7834 bytes .../odata/helper/FluentHelperCount.class | Bin 0 -> 2962 bytes .../odata/helper/FluentHelperCreate.class | Bin 0 -> 7121 bytes .../odata/helper/FluentHelperDelete.class | Bin 0 -> 7666 bytes .../odata/helper/FluentHelperExecutable.class | Bin 0 -> 1506 bytes .../odata/helper/FluentHelperFactory.class | Bin 0 -> 12231 bytes .../odata/helper/FluentHelperFunction.class | Bin 0 -> 11111 bytes .../helper/FluentHelperModification.class | Bin 0 -> 1803 bytes .../odata/helper/FluentHelperRead.class | Bin 0 -> 12339 bytes .../odata/helper/FluentHelperUpdate.class | Bin 0 -> 12084 bytes .../odata/helper/ModificationResponse.class | Bin 0 -> 9623 bytes .../odata/helper/ModifyPatchStrategy.class | Bin 0 -> 1457 bytes .../odata/helper/ODataEntitySerializer.class | Bin 0 -> 10098 bytes .../datamodel/odata/helper/OneToOneLink.class | Bin 0 -> 1480 bytes .../sdk/datamodel/odata/helper/Order.class | Bin 0 -> 943 bytes .../SingleValuedFluentHelperFunction.class | Bin 0 -> 1561 bytes .../datamodel/odata/helper/VdmComplex.class | Bin 0 -> 3158 bytes .../datamodel/odata/helper/VdmEntity.class | Bin 0 -> 10751 bytes .../odata/helper/VdmEntityUtil.class | Bin 0 -> 3311 bytes .../odata/helper/VdmMediaEntity.class | Bin 0 -> 6797 bytes .../datamodel/odata/helper/VdmObject.class | Bin 0 -> 12571 bytes .../BatchChangeSetFluentHelperBasic.class | Bin 0 -> 8751 bytes .../helper/batch/BatchFluentHelperBasic.class | Bin 0 -> 11623 bytes .../helper/batch/BatchRequestChangeSet.class | Bin 0 -> 3499 bytes .../BatchRequestChangeSetOperation.class | Bin 0 -> 3853 bytes .../helper/batch/BatchRequestOperation.class | Bin 0 -> 1121 bytes .../batch/BatchRequestRead$GetAll.class | Bin 0 -> 9494 bytes .../batch/BatchRequestRead$GetByKey.class | Bin 0 -> 8147 bytes .../BatchRequestRead$GetFunctionRequest.class | Bin 0 -> 8193 bytes .../odata/helper/batch/BatchRequestRead.class | Bin 0 -> 6639 bytes .../odata/helper/batch/BatchResponse.class | Bin 0 -> 3825 bytes .../helper/batch/BatchResponseChangeSet.class | Bin 0 -> 1217 bytes .../odata/helper/batch/BatchService.class | Bin 0 -> 1080 bytes .../batch/DefaultBatchResponseChangeSet.class | Bin 0 -> 6351 bytes .../batch/DefaultBatchResponseResult.class | Bin 0 -> 11146 bytes .../batch/FluentHelperBatchChangeSet.class | Bin 0 -> 1367 bytes .../batch/FluentHelperBatchEndChangeSet.class | Bin 0 -> 1111 bytes .../batch/FluentHelperServiceBatch.class | Bin 0 -> 2890 bytes .../FluentHelperServiceBatchExecute.class | Bin 0 -> 1273 bytes .../adapter/AbstractCalendarAdapter.class | Bin 0 -> 4031 bytes .../AbstractJacksonCalendarDeserializer.class | Bin 0 -> 4443 bytes .../AbstractJacksonCalendarSerializer.class | Bin 0 -> 4445 bytes .../odata/adapter/IdentityConverter.class | Bin 0 -> 4278 bytes .../JacksonLocalDateTimeDeserializer.class | Bin 0 -> 3074 bytes .../JacksonLocalDateTimeSerializer.class | Bin 0 -> 3066 bytes .../JacksonLocalTimeDeserializer.class | Bin 0 -> 3026 bytes .../adapter/JacksonLocalTimeSerializer.class | Bin 0 -> 3014 bytes .../JacksonZonedDateTimeDeserializer.class | Bin 0 -> 3074 bytes .../JacksonZonedDateTimeSerializer.class | Bin 0 -> 3066 bytes .../odata/adapter/LocalDateTimeAdapter.class | Bin 0 -> 3834 bytes .../LocalDateTimeCalendarConverter.class | Bin 0 -> 7154 bytes .../odata/adapter/LocalTimeAdapter.class | Bin 0 -> 3758 bytes .../adapter/LocalTimeCalendarConverter.class | Bin 0 -> 6074 bytes .../odata/adapter/ODataBinaryAdapter.class | Bin 0 -> 3908 bytes .../odata/adapter/ODataBooleanAdapter.class | Bin 0 -> 3056 bytes .../adapter/ODataCustomFieldAdapter.class | Bin 0 -> 3840 bytes ...ateTimeOffsetStringCalendarConverter.class | Bin 0 -> 6761 bytes ...ODataDateTimeStringCalendarConverter.class | Bin 0 -> 6091 bytes .../datamodel/odata/adapter/ODataField.class | Bin 0 -> 2331 bytes .../ODataTimeStringCalendarConverter.class | Bin 0 -> 7717 bytes ...DataVdmEntityAdapter$ODataV2Metadata.class | Bin 0 -> 6771 bytes .../odata/adapter/ODataVdmEntityAdapter.class | Bin 0 -> 7834 bytes .../ODataVdmEntityAdapterFactory.class | Bin 0 -> 2738 bytes .../adapter/ODataVdmEntityListAdapter.class | Bin 0 -> 5523 bytes .../odata/adapter/ZonedDateTimeAdapter.class | Bin 0 -> 3857 bytes .../ZonedDateTimeCalendarConverter.class | Bin 0 -> 5280 bytes .../datamodel/odata/annotation/Key.class | Bin 0 -> 310 bytes .../NoSuchEntityFieldException.class | Bin 0 -> 2151 bytes .../ODataPayloadParsingFailedException.class | Bin 0 -> 1616 bytes .../LocalDateTimeCalendarConverterTest.class | Bin 0 -> 4564 bytes .../LocalTimeCalendarConverterTest.class | Bin 0 -> 3403 bytes .../adapter/ZonedDateTimeAdapterTest.class | Bin 0 -> 2567 bytes .../ZonedDateTimeCalendarConverterTest.class | Bin 0 -> 4968 bytes ...NavigationPropertyTest$RelatedEntity.class | Bin 0 -> 7537 bytes ...ViaNavigationPropertyTest$TestEntity.class | Bin 0 -> 9357 bytes ...avigationPropertyTest$TestEntityLink.class | Bin 0 -> 5951 bytes .../CreateViaNavigationPropertyTest.class | Bin 0 -> 7945 bytes ...erTest$LocalDateTimeBooleanConverter.class | Bin 0 -> 7074 bytes ...tomFieldTypeConverterTest$TestEntity.class | Bin 0 -> 7735 bytes .../helper/CustomFieldTypeConverterTest.class | Bin 0 -> 8279 bytes ...uentHelperEtagParsingTest$TestEntity.class | Bin 0 -> 6232 bytes .../helper/FluentHelperEtagParsingTest.class | Bin 0 -> 9281 bytes ...tSingleDeserializationTest$TestInput.class | Bin 0 -> 7421 bytes ...gleDeserializationTest$TestingEntity.class | Bin 0 -> 7241 bytes ...ctionImportSingleDeserializationTest.class | Bin 0 -> 8359 bytes ...ntityTest$SingleEntityFunctionImport.class | Bin 0 -> 7887 bytes ...ImportSingleEntityTest$TestingEntity.class | Bin 0 -> 6919 bytes .../FunctionImportSingleEntityTest.class | Bin 0 -> 7346 bytes .../helper/HttpResponseEvaluationTest.class | Bin 0 -> 10450 bytes .../ModificationResponseTest$TestObject.class | Bin 0 -> 8305 bytes .../helper/ModificationResponseTest.class | Bin 0 -> 5856 bytes .../ODataBatchRequestTest$TestBatch.class | Bin 0 -> 6853 bytes ...aBatchRequestTest$TestBatchChangeset.class | Bin 0 -> 6962 bytes .../ODataBatchRequestTest$TestEntity.class | Bin 0 -> 8800 bytes .../odata/helper/ODataBatchRequestTest.class | Bin 0 -> 7895 bytes .../odata/helper/ODataChangedFieldsTest.class | Bin 0 -> 8245 bytes .../helper/ODataEntitySerializerTest.class | Bin 0 -> 8234 bytes .../helper/ODataFilterTest$TestEntity.class | Bin 0 -> 5994 bytes .../helper/ODataFilterTest$TestInput.class | Bin 0 -> 6491 bytes .../odata/helper/ODataFilterTest.class | Bin 0 -> 6260 bytes ...derTest$TestEntityCreateFluentHelper.class | Bin 0 -> 6709 bytes ...derTest$TestEntityDeleteFluentHelper.class | Bin 0 -> 6709 bytes ...eaderTest$TestEntityReadFluentHelper.class | Bin 0 -> 6585 bytes ...derTest$TestEntityUpdateFluentHelper.class | Bin 0 -> 6709 bytes .../odata/helper/ODataHeaderTest.class | Bin 0 -> 10272 bytes ...andTest$TestEntity$TestEntityBuilder.class | Bin 0 -> 8350 bytes .../ODataSelectExpandTest$TestEntity.class | Bin 0 -> 10874 bytes ...electExpandTest$TestEntitySelectable.class | Bin 0 -> 583 bytes ...ExpandTest$TestEntitySelectableField.class | Bin 0 -> 6340 bytes ...tExpandTest$TestEntitySelectableLink.class | Bin 0 -> 7551 bytes ...est$TestEntitySelectableOneToOneLink.class | Bin 0 -> 6944 bytes .../odata/helper/ODataSelectExpandTest.class | Bin 0 -> 6827 bytes ...ionTest$TestEntity$TestEntityBuilder.class | Bin 0 -> 11775 bytes .../ODataSerializationTest$TestEntity.class | Bin 0 -> 12085 bytes .../odata/helper/ODataSerializationTest.class | Bin 0 -> 6728 bytes ...ImportBatchIntegrationTest$TestBatch.class | Bin 0 -> 6142 bytes ...chIntegrationTest$TestBatchChangeset.class | Bin 0 -> 5301 bytes ...V2FunctionImportBatchIntegrationTest.class | Bin 0 -> 5723 bytes ...ortGetBatchIntegrationTest$TestBatch.class | Bin 0 -> 6182 bytes ...unctionImportGetBatchIntegrationTest.class | Bin 0 -> 5221 bytes ...tionImportIntegrationTest$TestEntity.class | Bin 0 -> 7513 bytes ...ODataV2FunctionImportIntegrationTest.class | Bin 0 -> 8897 bytes .../helper/PaginationIntegrationTest.class | Bin 0 -> 9695 bytes ...ionUnitTest$Customer$CustomerBuilder.class | Bin 0 -> 6504 bytes .../helper/PaginationUnitTest$Customer.class | Bin 0 -> 8451 bytes .../PaginationUnitTest$CustomerField.class | Bin 0 -> 5654 bytes ...aginationUnitTest$CustomerSelectable.class | Bin 0 -> 565 bytes .../odata/helper/PaginationUnitTest.class | Bin 0 -> 22714 bytes .../helper/SerializerRaceConditionTest.class | Bin 0 -> 3778 bytes .../helper/ServicePathTest$MediaEntity.class | Bin 0 -> 7417 bytes .../ServicePathTest$RelatedEntity.class | Bin 0 -> 8992 bytes .../ServicePathTest$RelatedEntityLink.class | Bin 0 -> 5927 bytes .../helper/ServicePathTest$TestEntity.class | Bin 0 -> 8810 bytes .../ServicePathTest$TestEntityLink.class | Bin 0 -> 5912 bytes .../ServicePathTest$TransitiveEntity.class | Bin 0 -> 7167 bytes .../odata/helper/ServicePathTest.class | Bin 0 -> 7872 bytes .../SpecialCharactersTest$Ticket1.class | Bin 0 -> 7677 bytes .../SpecialCharactersTest$Ticket2.class | Bin 0 -> 7877 bytes .../odata/helper/SpecialCharactersTest.class | Bin 0 -> 6932 bytes ...TestVdmComplex$TestVdmComplexBuilder.class | Bin 0 -> 8162 bytes .../odata/helper/TestVdmComplex.class | Bin 0 -> 8496 bytes .../TestVdmEntity$TestVdmEntityBuilder.class | Bin 0 -> 10628 bytes .../odata/helper/TestVdmEntity.class | Bin 0 -> 13165 bytes ...checkedFilterExpressionTest$MyEntity.class | Bin 0 -> 5131 bytes ...kedFilterExpressionTest$MySelectable.class | Bin 0 -> 3626 bytes .../UncheckedFilterExpressionTest.class | Bin 0 -> 5719 bytes .../odata/helper/VdmComplexTest.class | Bin 0 -> 5865 bytes .../helper/VdmEntityTest$TestEntity.class | Bin 0 -> 8291 bytes .../odata/helper/VdmEntityTest.class | Bin 0 -> 12915 bytes ...tityVersionIdentifierTest$TestEntity.class | Bin 0 -> 8660 bytes .../VdmEntityVersionIdentifierTest.class | Bin 0 -> 7586 bytes .../ODataV2BatchFunctionImportTest.class | Bin 0 -> 10677 bytes ...questUnitTest$TestParameter$Executor.class | Bin 0 -> 5703 bytes ...V2BatchRequestUnitTest$TestParameter.class | Bin 0 -> 6932 bytes .../batch/ODataV2BatchRequestUnitTest.class | Bin 0 -> 9320 bytes .../batch/ODatav2BatchConnectionTest.class | Bin 0 -> 9320 bytes .../TestVdmEntityBatch$TestEntityByKey.class | Bin 0 -> 6281 bytes .../TestVdmEntityBatch$TestEntityCreate.class | Bin 0 -> 5759 bytes .../TestVdmEntityBatch$TestEntityDelete.class | Bin 0 -> 5759 bytes .../TestVdmEntityBatch$TestEntityRead.class | Bin 0 -> 5837 bytes .../TestVdmEntityBatch$TestEntityUpdate.class | Bin 0 -> 5759 bytes ...unctionImportCollectionResultHttpGet.class | Bin 0 -> 6687 bytes ...tityBatch$TestFunctionImportHttpPost.class | Bin 0 -> 6619 bytes ...ctionImportSingleEntityResultHttpGet.class | Bin 0 -> 6684 bytes ...estFunctionImportSingleResultHttpGet.class | Bin 0 -> 6662 bytes ...dmEntityBatch$TestVdmEntityChangeset.class | Bin 0 -> 6382 bytes ...mEntityBatch$TestVdmEntitySelectable.class | Bin 0 -> 539 bytes .../helper/batch/TestVdmEntityBatch.class | Bin 0 -> 7789 bytes .../adapter/LocalDateTimeAdapterTest.class | Bin 0 -> 4182 bytes .../odata/adapter/LocalTimeAdapterTest.class | Bin 0 -> 4032 bytes .../adapter/ODataBinaryAdapterTest.class | Bin 0 -> 4541 bytes .../adapter/ODataCustomFieldAdapterTest.class | Bin 0 -> 10068 bytes ...aDateTimeStringCalendarConverterTest.class | Bin 0 -> 7715 bytes ...itivesEntity$PrimitivesEntityBuilder.class | Bin 0 -> 12358 bytes ...rimitiveAdapterTest$PrimitivesEntity.class | Bin 0 -> 12530 bytes .../adapter/ODataPrimitiveAdapterTest.class | Bin 0 -> 6731 bytes .../adapter/ZonedDateTimeAdapterTest.class | Bin 0 -> 5258 bytes .../BatchRequestFunctionImportWithGet.txt | 10 + .../BatchRequestFunctionImportWithPost.txt | 17 + ...FunctionImportWithPostWithCustomHeader.txt | 18 + .../BatchResponse.txt | 90 ++++ .../BatchRequest.txt | 83 ++++ .../BatchRequestWithCustomHeaders.txt | 90 ++++ .../BatchResponse.txt | 63 +++ .../BatchResponseWithChangeset.txt | 62 +++ .../BatchResponseWithError.txt | 29 ++ .../BatchResponseWithoutChangeset.txt | 19 + .../odata-core-apache-httpclient5/pom.xml | 126 +++++ .../odata/adapter/ODataNumberSerializer.java | 40 ++ .../CollectionValuedFluentHelperFunction.java | 32 ++ .../datamodel/odata/helper/EntityField.java | 219 +++++++++ .../odata/helper/EntityFieldAll.java | 50 ++ .../datamodel/odata/helper/EntityLink.java | 180 ++++++++ .../odata/helper/EntitySelectable.java | 37 ++ .../odata/helper/ExpressionFluentHelper.java | 156 +++++++ .../odata/helper/FluentHelperBasic.java | 234 ++++++++++ .../odata/helper/FluentHelperByKey.java | 113 +++++ .../odata/helper/FluentHelperCount.java | 71 +++ .../odata/helper/FluentHelperCreate.java | 148 ++++++ .../odata/helper/FluentHelperDelete.java | 128 +++++ .../odata/helper/FluentHelperExecutable.java | 38 ++ .../odata/helper/FluentHelperFactory.java | 280 +++++++++++ .../odata/helper/FluentHelperFunction.java | 216 +++++++++ .../helper/FluentHelperModification.java | 31 ++ .../odata/helper/FluentHelperRead.java | 283 ++++++++++++ .../odata/helper/FluentHelperUpdate.java | 293 ++++++++++++ .../odata/helper/ModificationResponse.java | 154 +++++++ .../odata/helper/ModifyPatchStrategy.java | 23 + .../odata/helper/ODataEntitySerializer.java | 321 +++++++++++++ .../datamodel/odata/helper/OneToOneLink.java | 24 + .../sdk/datamodel/odata/helper/Order.java | 17 + .../SingleValuedFluentHelperFunction.java | 32 ++ .../datamodel/odata/helper/VdmComplex.java | 15 + .../sdk/datamodel/odata/helper/VdmEntity.java | 226 +++++++++ .../datamodel/odata/helper/VdmEntityUtil.java | 54 +++ .../odata/helper/VdmMediaEntity.java | 97 ++++ .../sdk/datamodel/odata/helper/VdmObject.java | 373 +++++++++++++++ .../BatchChangeSetFluentHelperBasic.java | 216 +++++++++ .../helper/batch/BatchFluentHelperBasic.java | 202 ++++++++ .../helper/batch/BatchRequestChangeSet.java | 32 ++ .../batch/BatchRequestChangeSetOperation.java | 18 + .../helper/batch/BatchRequestOperation.java | 13 + .../odata/helper/batch/BatchRequestRead.java | 59 +++ .../odata/helper/batch/BatchResponse.java | 102 ++++ .../helper/batch/BatchResponseChangeSet.java | 22 + .../odata/helper/batch/BatchService.java | 20 + .../batch/DefaultBatchResponseChangeSet.java | 30 ++ .../batch/DefaultBatchResponseResult.java | 134 ++++++ .../batch/FluentHelperBatchChangeSet.java | 28 ++ .../batch/FluentHelperBatchEndChangeSet.java | 21 + .../batch/FluentHelperServiceBatch.java | 71 +++ .../FluentHelperServiceBatchExecute.java | 23 + .../adapter/AbstractCalendarAdapter.java | 87 ++++ .../AbstractJacksonCalendarDeserializer.java | 89 ++++ .../AbstractJacksonCalendarSerializer.java | 88 ++++ .../odata/adapter/IdentityConverter.java | 41 ++ .../JacksonLocalDateTimeDeserializer.java | 39 ++ .../JacksonLocalDateTimeSerializer.java | 39 ++ .../adapter/JacksonLocalTimeDeserializer.java | 39 ++ .../adapter/JacksonLocalTimeSerializer.java | 39 ++ .../JacksonZonedDateTimeDeserializer.java | 39 ++ .../JacksonZonedDateTimeSerializer.java | 39 ++ .../odata/adapter/LocalDateTimeAdapter.java | 36 ++ .../LocalDateTimeCalendarConverter.java | 73 +++ .../odata/adapter/LocalTimeAdapter.java | 35 ++ .../adapter/LocalTimeCalendarConverter.java | 71 +++ .../odata/adapter/ODataBinaryAdapter.java | 54 +++ .../odata/adapter/ODataBooleanAdapter.java | 43 ++ .../adapter/ODataCustomFieldAdapter.java | 112 +++++ ...DateTimeOffsetStringCalendarConverter.java | 91 ++++ .../ODataDateTimeStringCalendarConverter.java | 66 +++ .../datamodel/odata/adapter/ODataField.java | 36 ++ .../ODataTimeStringCalendarConverter.java | 124 +++++ .../odata/adapter/ODataVdmEntityAdapter.java | 301 ++++++++++++ .../adapter/ODataVdmEntityAdapterFactory.java | 34 ++ .../adapter/ODataVdmEntityListAdapter.java | 103 +++++ .../odata/adapter/ZonedDateTimeAdapter.java | 36 ++ .../ZonedDateTimeCalendarConverter.java | 51 ++ .../datamodel/odata/annotation/Key.java | 17 + .../exception/NoSuchEntityFieldException.java | 49 ++ .../ODataPayloadParsingFailedException.java | 37 ++ .../LocalDateTimeCalendarConverterTest.java | 77 ++++ .../LocalTimeCalendarConverterTest.java | 60 +++ .../adapter/ZonedDateTimeAdapterTest.java | 32 ++ .../ZonedDateTimeCalendarConverterTest.java | 84 ++++ .../CreateViaNavigationPropertyTest.java | 236 ++++++++++ .../helper/CustomFieldTypeConverterTest.java | 256 ++++++++++ .../helper/FluentHelperEtagParsingTest.java | 368 +++++++++++++++ ...nctionImportSingleDeserializationTest.java | 153 ++++++ .../FunctionImportSingleEntityTest.java | 260 +++++++++++ .../helper/HttpResponseEvaluationTest.java | 197 ++++++++ .../helper/ModificationResponseTest.java | 175 +++++++ .../odata/helper/ODataBatchRequestTest.java | 436 ++++++++++++++++++ .../odata/helper/ODataChangedFieldsTest.java | 118 +++++ .../helper/ODataEntitySerializerTest.java | 104 +++++ .../odata/helper/ODataFilterTest.java | 111 +++++ .../odata/helper/ODataHeaderTest.java | 222 +++++++++ .../odata/helper/ODataSelectExpandTest.java | 215 +++++++++ .../odata/helper/ODataSerializationTest.java | 342 ++++++++++++++ ...aV2FunctionImportBatchIntegrationTest.java | 96 ++++ ...FunctionImportGetBatchIntegrationTest.java | 83 ++++ .../ODataV2FunctionImportIntegrationTest.java | 232 ++++++++++ .../helper/PaginationIntegrationTest.java | 134 ++++++ .../odata/helper/PaginationUnitTest.java | 232 ++++++++++ .../helper/SerializerRaceConditionTest.java | 46 ++ .../odata/helper/ServicePathTest.java | 433 +++++++++++++++++ .../odata/helper/SpecialCharactersTest.java | 252 ++++++++++ .../odata/helper/TestVdmComplex.java | 79 ++++ .../datamodel/odata/helper/TestVdmEntity.java | 298 ++++++++++++ .../helper/UncheckedFilterExpressionTest.java | 86 ++++ .../odata/helper/VdmComplexTest.java | 96 ++++ .../datamodel/odata/helper/VdmEntityTest.java | 162 +++++++ .../VdmEntityVersionIdentifierTest.java | 354 ++++++++++++++ .../batch/ODataV2BatchFunctionImportTest.java | 251 ++++++++++ .../batch/ODataV2BatchRequestUnitTest.java | 347 ++++++++++++++ .../batch/ODatav2BatchConnectionTest.java | 194 ++++++++ .../helper/batch/TestVdmEntityBatch.java | 371 +++++++++++++++ .../adapter/LocalDateTimeAdapterTest.java | 60 +++ .../odata/adapter/LocalTimeAdapterTest.java | 64 +++ .../odata/adapter/ODataBinaryAdapterTest.java | 63 +++ .../adapter/ODataCustomFieldAdapterTest.java | 216 +++++++++ ...taDateTimeStringCalendarConverterTest.java | 161 +++++++ .../adapter/ODataPrimitiveAdapterTest.java | 260 +++++++++++ .../adapter/ZonedDateTimeAdapterTest.java | 90 ++++ .../BatchRequestFunctionImportWithGet.txt | 10 + .../BatchRequestFunctionImportWithPost.txt | 17 + ...FunctionImportWithPostWithCustomHeader.txt | 18 + .../BatchResponse.txt | 90 ++++ .../BatchRequest.txt | 83 ++++ .../BatchRequestWithCustomHeaders.txt | 90 ++++ .../BatchResponse.txt | 63 +++ .../BatchResponseWithChangeset.txt | 62 +++ .../BatchResponseWithError.txt | 29 ++ .../BatchResponseWithoutChangeset.txt | 19 + datamodel/pom.xml | 1 + 324 files changed, 16105 insertions(+) create mode 100644 datamodel/odata-core-apache-httpclient5/bin/pom.xml create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetAll.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetFunctionRequest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest$LocalDateTimeBooleanConverter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatchChangeset.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest$TestInput.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityDeleteFluentHelper.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityReadFluentHelper.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity$TestEntityBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableOneToOneLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest$TestBatch.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$Customer$CustomerBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$Customer.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerSelectable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$MediaEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntityLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest$Ticket1.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest$Ticket2.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity$TestVdmEntityBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MySelectable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest$TestParameter$Executor.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest$TestParameter.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityByKey.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityUpdate.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportCollectionResultHttpGet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportHttpPost.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest$PrimitivesEntity$PrimitivesEntityBuilder.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest$PrimitivesEntity.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt create mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt create mode 100644 datamodel/odata-core-apache-httpclient5/pom.xml create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt create mode 100644 datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt diff --git a/datamodel/odata-core-apache-httpclient5/bin/pom.xml b/datamodel/odata-core-apache-httpclient5/bin/pom.xml new file mode 100644 index 0000000000..04351015c3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + com.sap.cloud.sdk.datamodel + datamodel-parent + 5.35.0-SNAPSHOT + + odata-core-apache-httpclient5 + jar + Data Model - OData Services - Core (HttpClient 5) + OData Services data model (VDM) - core classes using Apache HttpClient 5. + https://sap.github.io/cloud-sdk/docs/java/getting-started + + SAP SE + https://www.sap.com + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + SAP + cloudsdk@sap.com + SAP SE + https://www.sap.com + + + + false + + + + com.sap.cloud.sdk.datamodel + odata-client-apache-httpclient5 + ${project.version} + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-core + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-connectivity + + + com.sap.cloud.sdk.cloudplatform + connectivity-apache-httpclient5 + + + com.sap.cloud.sdk.datamodel + fluent-result + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + com.google.code.gson + gson + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.client5 + httpclient5 + + + io.vavr + vavr + + + + org.projectlombok + lombok + provided + + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + org.wiremock + wiremock + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class new file mode 100644 index 0000000000000000000000000000000000000000..0a0caba4121f7c1fb225c6b2212949950817ae32 GIT binary patch literal 3244 zcmeHJ-EY${5I>Kyu4QbnF}`7D4CnwQFGxrfA@MOpQN|D*lX&zJuQeogu$^k-A438O z-g)K!L7bD7Wl(652I7H-IQ}@_o$u~&7Ol^-$75*6{-S8nRFh^3ZmfAs;bo@B`e*KMu^i^UtRt56|uBcq7{4u~uy+U?Xe_N0sN6?}Rj3h|-lbjETImTmM8@ZzOU;dndpx43P%2_(g_1PY zY9r=}xkc->wJxW2JLS|d@SHHO5Jp2LrLuH`(>@cAwk#Ef3KAxhZP zD#7*Z49_C!g)uXRZi<+jzbPf+DA*~GvhWq_aJqmm_s|un5W2!cS9srC;pMLIc3iPQ zBgGAMk4~5kyVRm>_*RN9E%QT1d(P2bZn4q17gi8SsV6=PZkDncAZ(nX&E! zsVI}qhXvyj%X;mxQF0e?1MESqqAUkTy;cBp2+?YfYb~Ndw%x-^Ntwm*5N1xjWke~L z__!aS`1|}udcl*CR9kcTVn1{$yC6?Z_m#AKYuJNZNYrr=@=LR{yAEi-rf~{9)glpA z^oBj@p6EQ_p-9*$p4)e(e?od4CgAWS9E2$XbB$oPgtV;|veR@WW;#F+6|u9%uxb#P z2~^19b%wPH*E^dP+ZBeuYOt?mut}h1`bCkz%|=jY(hOV(HiMtVPje|h+JO&pIrA)& zRwTS7d%_4*`>v~PdB~yG^`MVjCotFn0nQh{zxI@AmT)ZFrTY_bnv8uOq15(;42j9n zF!(<`>}gHP`S5>w_^xj&8|ODN9s;ASG9B`V*u)VyJ+M>FyC6hOTXZC|nMT6vrA$&3 z?!8Fm`gR}3cMxQAE$QmAFcwSQBf&M(VRyR<7YH0GMR^r25;*xY#8=@GfvNJTYof1U z$6RELDlB2U?6L-d$rWJ?<~O%8Z~?HJ#zr1s3}**n9LFlIZs0D#fBij7yqKxKg~^XO z%`}dV2JkzA<77bvM`0%4n}uU|a}MWqhTk7Ui)os)GB4s8a((%?=P% gfirk#49>#2{C^(i@egnnzbRCa!U8P9WoW>GuZQa;&j0`b literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class new file mode 100644 index 0000000000000000000000000000000000000000..142e7887e6de3c1c99d35c19fe73f9f767e12f67 GIT binary patch literal 1566 zcmchX-%k`V5XYy-$#Lr`h=RY9LVS>y-FJ?V7{W#3i3U~@V|?iC4tIrayS7~dZ~j*% zn&_Jk{!PX>yLZTuT+D?;AGYo6wDX-#KkbiSpT7dYHau=Xjlnx-lPD8XM@QmBM2X2F zC-Nv!rbr`~9!IJ0A~Pwf$dtoqfGS5DZ5gF7@zUr6p$bep)M)>_a&`(`c^Xh>@ZyRB zlL9iB-H=-PO$JL(yH&zU=zg^Ct$Gnl^!xoxenb8v&PpKr89XVeL%oXV3+RR!U>H55K`>pW=2 z1LXcJM_vX9%bdbjy(6j*Pedupd&tj&gnY+y>A2LXv@}JXk?RmG>Vy;yt;xv#P>?U> zVWNa{{7@=%|5GJJ5-t#JIyPAG8D9G&e1ANPif>ZpR|kea_w3Lt>_ z7F>f_qN6J{?iN`OZ7k^M87y{9BGiGfvb0A(>wX}Ku=m|7f=3pL)~4A|#Q|N*ec9K-7nat2-?${TK6c-S zqV19P1kWg$37*ZXR%bk!^9OF4YBb;>gN2KSWk?-^)=TLey;?DyoU5+@gBg03fqGWr z1~q7q)hNw58Z_za(3BeW!50YLF19|w&2MDQzyh_iG_PEIi`rH-4qy>(lXd6PpCz(p VV42z)+=Y8pe;*!EU#C?MegkxR{XhT! literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class new file mode 100644 index 0000000000000000000000000000000000000000..a271bc16571d397c981e690fdc52f8afb516e83e GIT binary patch literal 12029 zcmeHNS$Eq+6uxrPdTF|)8#INg3y_r70$R2@q^C=g2Hd1=+@^)HDE1^)BTJ4X=cLd4 z0$$;PU%+$Eq36Ie`~ZFq@XbiFB}a|NiBifrd9bXJ?#y@Zox9B3(Vu_+`Wq2V(05tN zD0JO*s#-%gEbWQDs%urdp}BfftD1JJrn$9ct)_c=!>%z?v-z!7XJ(T*+Jxm9-qT5g znYAnpD0EPCH+5@Cn_YOqs$Q1%C^WUD(dU?nf%FBFDU@I2P&0Z1_e13l*qrAW)>28K z;d?Ex)L_%rs^J>wbIr1BPxlPla?!m!V_R0sG(njwPchG94sQ2(Pn+z9ZLKl~4-`63 zu0$VzB0py=S-RJ9fO{hTcB~@AuawS@gRoGZ!SI|}GzJB?_bonrO`-i|K^e61e$SX; z8Q!=;M@QDnGWtNFbKTP1(H+PRCSzg^+`!Ca={0cPQOShk*iMd4C_~TgTMlz=bCuOp z%xu#zg+A1#V}s9zdqpkeD|M!FHdQ`AkxyXJpFp*$a}BBsObrJqs?(loxT;~*37uadqHoh@)SAs)mCM}Fy=qA`&+7>%awctY1} z2@39?D8TPE)~ifr+KmN!xj3=X(oOf8RlB9Tb#B+aWD5HE&YVchXDH!?>e;F;jJ}Xh zhlD#3yt8h@&vZ*jGU9vDsP7isl!TW>;^tUYZ}Px$_R>XrQN4O0Y+e@4ao@FE&MfcU zFkBar&L71e+qaegcZqo-$nX$3vOTDNtlb8kN%QV&uX4c;yc?>h4`w5?_v&y`54-?# z3F}yAJKdUSv|LZMk;fbZo?|;IA`L=3vpkr(+d|3~L-@ubHHFcXwRI$nolvKv}! zx11v1Bs)cH&_e$I&c8%t+N@H;NYj7d>C&+TkLgZ>AM<;dqZE+@>*LIEEDUQPBe-G&-$zqPyW5|AJ)6&}u_XttF zNfAlSKtCeNnPMMtNp3(XBbi24HmZi_r(|r(EZ1{dRSyZjC{4fla5 zH)WEukveYb1O8Wn?BKSGIMDztX?=d_EWshRX{-Vrr;~$pn2zw09OB?itFgeGieCyG zD%(}ve4sl9ZwC(tyt;up-_7!tDvrY5QJ*^&hrn(nfSFHW{IN=~sVRj8L|#8Umrx0e z*BeW`FsSt;iKo_;tNwbAL=qNm~v(;AIucWuoZ=>QC<7Vrj))4Ya^ii2Q|kQ z(Fz@cs$-9!$dldAqG=;X(~9y?MwP*lR}VGCz*D46OS4iN2`M7EIYLV2XB}O~e_@9C z63!9vg4v*`i%A<6kJmJDFei@kv@jCG{AZQylEj>)SJmn@lRJkZI5+I?tSMg66)TpW z2ReO}$o+UFRsMe^IoUZB;a@?F^)chI(7SUevMr~vT{@Nsu<~0GnFDI6G$=(R!FqZm zy$(D$QZJgcr0Vlf1gqTeoIgkR6gs_e-o+LHJ;LCn(5{iuX{?Ut?3PnyllVdg%QoMq zg<*&~k?cC!ybM{|g6B?9yq1n(*6=>kwrrhxmLrw+r`MhkJ|OZo|DJv=B-g z;&A4DoRI{vW+cTrPw&z|Al8K#PFt5z;X1m!m;m}%5>(rwa7&Un@1-eROA_?`G(o?Y z1pOdQ&>tj0FKrg|a)RyrED8G2WJpUHI0^IQteFZIl{V(j`_ND*; literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class new file mode 100644 index 0000000000000000000000000000000000000000..11356b593bf224c9f77bb95efec5888411774207 GIT binary patch literal 4713 zcmd^C-EQ1O6h5;F*&k98(zH-oU`Pwi7GhKW>8=)4Re~aIQ`Ce=a5u4Mv(t_}%Xl0$ z@4>4eB#_{q7eG7;H;6MHn|RrUm^4jQapAScK6}17-#K%>nP2|+;U@rChfnKJBe0(6 zz>k=S{RixT`GJc3gpK?_sxnPPOSsa&fIe|rvZy&W#}HK zg%gi%8Y&Vh;!tRulRU&v1Fm@-Ai_Ya2*=-~_${PfAX$>ofsi~oOO%i!u${n&1rOOi zrz;q_hxx<_VLm;~r}tMry)2)e%jXIW6;G&*IbtRlQiIg+M<)A3*<$fB!YVs= z9EfP7v~gUgM#w27pWN_~mu1Z5crtqvj*qq9j7Cxf!q6neddCuq6Qk3>C{10BthQ}A zq;bdxBDN)fU+|fw(v+<|dN)ms{eo7M#gvz%`EX zHrRE;ogR&B&Q*~utPCuE>^eL)n9Ho9vcBUSSI+AeR*3C|Q>ZC3{f|AYY*dPCpBvX_ zE$tXw*34`J$mZx`I%>q-fQ?M5`PG|i$iinm*9H^o+x{mKNn(pH`>!nvxAbxft3VZ^ za>QoVX#WkN;<_lsJY>nx3drf_vIO#EqHRG77UrQ2iv%vL_K(ERRcFVF<3b<1%~l%i zalPZzbE&TaCU=<@cAUSQHA9i0M)w~}q>F@g2wWUTL2b>E?w<}scPC$BogSjiBf%N? z7K?C$u)NxyWP`xv`zdZ#BEA_Ph(uuUx2?8kN`bLA`*|Zof6Jx67bVqcz^eqlt+1Oo z)rMN7g%db8R$&Ah$bemQ7)x_o?8nS5R03D>*WFTld9m~`zN-X&J?r?+NHCC+qH8zc zEdsxvb(~f5nC(NKw8r568MmiQV#$v+4JQ_z_SXci{e7W)!89%`!}ZvTY4r;o!rFO< zki80=7OUwPWA?ujI4t8ru(IRNW&_?OWbyQfFNszG^b9H^f!4N4b-*|9X9ZWy@=?B3{gdZnJ4ucedvo!DuW#I6n>;*r;O%{qKMcGh+s z2F38M+j;Gv=qB(6V4>jk9G>;(@Lq=o{x)&6k-Z0K{RpkEmgc{Q7kx6!c)u|A<|2*=yaX>p4K8I?HFyQ@m+_j!xH1J}sUpT}@H)@%{XP2P2r+{6r2(|`Zo}dD( l@SmkND*%6r|Fuf~XW%BzvIg(v>3JVM!Epn_`v9+x{shkVt+W6D literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class new file mode 100644 index 0000000000000000000000000000000000000000..b65c0fac61de3e80d54217a7852a598671a69a37 GIT binary patch literal 14867 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class new file mode 100644 index 0000000000000000000000000000000000000000..9804c91a54ec3212c4d6611ba7e1c4234aa74f1b GIT binary patch literal 4469 zcmds4U2hvj6upzCact5i4YWX8Usg#;p;28c-o;$^Zswl`VNxU;iS zBJmS=;*B?c1xO$P&-^IFnO!GdH(Kk6EpB#~AdaVnMl zBzs(;Nk(&-r&y?#zrD9F1Iy?_tXquc>)N#ZByQf@_v^aVl4ga`jW1GVqmJBEhfxyk z#qtBCv=tVf6Gk`deP=sqH^Y1=jEYn%!N(U51<;-6Sa+#?|6Gn8Tm1ZEyYW;y$Dve|KoBR-D zRp?1Zv;EN5jnQVBuCl4hmkPY;_)vx%WOt)DOOE4g#vIU>EB zNFE4B4&Rgf#EHi@Esqi&sW39&IzGm$rZiFoKxi7>!T3>%cOmzJSR@H=MzKu(O_Wdq z*agrP!GUN=zJSa<$P58NW*%hboyyG1Wai~ETi~IV33oYngbmu}7U6cS8P`W%#&13&XSp7*@1YEtk^!?^m!3R=X4FvQtq99VwV;Mjas&ev zU_IqAL@LWT7m75sDi+WXC){c-GRf4cj)D;;c}ydWh9lLgJ8t=o__nyjMwo6Q;V6hK zPg0Z>kf0K4(!grNbImU&+7jI`yb^|I>}ju7N#X_UZ`~w>l8ADEPGv%cbrU7s0DAPDsb&g#Lz0r zKvM*=R-<`(dWJ61GmNe-7Q37^s0J&p`^?s%_y=icPnw26cgQ+uAmUwNA~)_moOIBP zw(F&~a{~X7NZ}=*pZC4fX1^LzXo96C{&1Fly*j+FP6*%wDwKeJ_p&o!zd|@ z-ShDF$7*A_P*d_wy0TWfO5KVrc~({%&gnG0#z>XM-B`+GbQcBvqcewwhmmdT)NZG! z-Pr$QbJmuLM8%zhdWy4xsNX;>C@t!u?-5&=f8LN7)pm4h0=a?X0gh$;LsRj9m+i0_ zd8@sOJxpb1g~zB;&-RTtbH2-HrkGfT-UpM99}HpC+ZQ*`b`pA#PzG>pouKPlk_GqCg`mmMfXmT-p2D~T+@j59bE7I4P3HMP5=M^ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class new file mode 100644 index 0000000000000000000000000000000000000000..0cd92d66e46be8f287f1790f4d1064d25b3d869b GIT binary patch literal 10440 zcmeHNTXz&i5UvRelPn@N zFFtzw37(_p;JZHi?k`clp4;wZ2d33AULQ7@>8`G?s=B(Sy1Kvr`I|>XbeSBP#w42a zT&-cLhTXWY_SJ^wSPf6@H8j%+^oFOeG<4NhEk|c&!{LvOE;D<~ZOobhv;Dd7<)Z2t znoJdmwueki)$TMFmhLmnmuQ3ObR^o@a@yKi{L zlF2UEw&Sb5;ni$SmFGS9oKy0&3=JPh90S0MuDY&zDGv(mW2Z04#y)6u!v zV{YHj*bUY1a*`oVz6%B*nP^>iY&$SbJdd}oFy9Yl+77J@e|5$&O^6RkB&xQqgG_~d zAeFmJ)tL+VHny&*Jx~i;)#ZTPE*F zbbRf!+(^XC%ui(oULn&n5^Wop_p<9c?l>vZj;C(hm@&ufGhKmK^$atdSf%GWu%zXk zSE>{3E>rx~9#c4jI(M6T2v~q7WL_aokDc&g6Fk|EBFet%8vIiP%ZD+% zK!fDVRX`@h3!kJ(MTAy!nN%{*-$Zzo_ zvG@|EUj=Jn-s+i#X84L1AYVobWP83FXujhrQ9P_o@^mJ!b+&BSOs^}~0?+4}MK}!U zjbZnl6{e_2J-S&A)d?{ZW_ zvRQ5px3o{i8@5xom=~CSdpg?Lc>Igcd02+$VbdD%ih_q|1Q45`VkL!aB}7yb*1y2& zLgp&d;r};%vL%|wj*4f|1tCdn^$1Z_=`bDJLI>zsiT31_VtC^r}QZ3K*!Q03j6Egop|Sf`uL!em63a zM6czntehg}P*XZ1=fsAN_+C?zz7R02P>}dXr}c$%6_$I~KU2bKz`#WiHxJapMU=ZN zn_-{evKoy;7bNNPb@qi~s|zhOA)=D7{+iScA5-oIHv{IbrUx41bm>2^eE;F|w#j4s zEKZ^`wN{e2O|@oFPzfxY*$tj@m-Lr|LCurDH<^hxXp5m7JTq&am`1jeBIg8*OfOa! zDnB?O)I;~f(9zg2FVSb~96J(YJ~=&EoaC^o>v5`b z!PaL`%yO)Bp&{;`s^t!R9J^PVF+%%%?zrh1a;EUav<+*fkK=ShqAO1}BtLo-`W{E; zu#3ZI$=R$l3Y$~o)RyR%CxeCuo||hW1)Aspw9^noJt5pI~le zf~9ulL@k+cHD%kH=q!_wxzZ38`(;|m+-`|hjZ79=*WPTAXv?*zV%Nq#+N&HTh=D6N z8|i8MHsMXi8~p84@LM^cp9+Ci=;9yKcP0n=Dvg~ z9E{_F7`L%#l9hZeRV*LpP7cnif;jI$@-ey>FQIo+r1#Kn?P5O%dg zxmSHcd$el%ooYb4RonGLry4j>P&tf6rn9c!JKHQMqql0dtBBDT&y&r<-62tRZ)(6t=j0w z!b1WV9)zCc9d^s>@qll*>>d@g!z2^Sp65#{x$gx8N{u_d=Y_6|D8&`rz5#Ot=C?ss zh494;%#%6mdrz>ycYDk+(R^L*MoP@C@KJpySTxI}W}BIEv&&4Sz*4KU?ruC`wlr<3 zV9gC?ZYi?Ov`p@5SWj-%YywxaTo%t z1$3$Xl(v|89(Qh`ag8Vi?J%OX-p3<#;j8%j=jGZK)2fS&#EVpiH9lba)V8;*0g+ zagFZ@S|^zAg6T8bv-QV3F_mGrExgV|kK35rWqWr^t6F0ROD7iJ)ylEK$|WtsjT@H} zjRL(`(b~ryEQ^!_=7fcouJy3n#lk1}feH_OH^|Ycw+lU~TUhgHY(rCQSqQqBwfx9A z(sgUj7SU{DNWTOhxq&>&y`AcxbnLH5*ZSgD1wvA5Vi;Q^*tM%z2 z2wnLN^O)dvhID#4DTzf?klWB7|Y^edfwC z%)*gbFyJVG;~8z6YSQK#++%mb&IS`r6`}-=Hhi1950jcD87#^+$6oE*#-w^!Z1zqJ z0%6aVfIXWS%2IP6wbzeI>bB#%8jjR4nw#?$n<`PuwJdERZDK|B!>WWbRqQyQKO}<79WioJo60B zRvry7_`-;_&!jXBSH8%s2YM85RcD||$cDx_3~R{Qx1Oc-q%@0SO|h3#I}

o-;d? zEa4*p_bZK5b&HX<$HL;F(}^<-9_q;QQqyO9Z3MIxrmw{i?!AGQb;Q|yB{P#w%hPMAcLXK|# zVg~%07W^-;0GImk9}H|?K^r8o#TICxzh^50^d~J)0w2N0eb7%b_q+@6ry0N-TEJ6q c1(#od&l7Wh0XOjd5FYqdScE00!`E>AKmQ2`5&!@I literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class new file mode 100644 index 0000000000000000000000000000000000000000..65bd22998e23e60d42eadc51b8953ee687140dec GIT binary patch literal 2962 zcmeHJ-EJF26h7l5cDyzHNok?9NYl^?8>-zKxFHBBq%BFcZBZJEdO6-bv8UOcS!QOf z^euP{-T(^`H{YC}@62C+|Mmv}Jb-&0XffEeCW&$( zRrFF!M3m?}vZ9EROqXe7)1xRAPUJepEYf}$jWH|GL=UqPmD{Z*JGxY^1FH-!S0I^C zqv+}IB_@tR$KX#TS{hxApWY{g{d!(4+G21Kk1;77PG1v7KfX`)QVA!u+8R7!aA%)3 zkU8$DiL`Q<;rGIb937f%rL?OIErV|SL@QNh8NsgZ5a$lO$KcW_rw7JpvksRS`}qfD z(CTc0DW@|RGOIv%VKl+#_A4HAr(ir0eiJ@KezL@AF1UMDV9;%FU1B+R`X%-UW8`$m zu}-%5lo8?nx*-y}`nizWLvD zZDi>>++y&{Tg$#?_^OIJ+siZIK8HPn>-_~s^f9>A3{6=SR2%k3VHCy4lAqQlf)v6B zCe!&)A4OGK35V&{I&8AF(K%e;3!qAJp9G;-m9SQo1&0jzp5xMTsZwc3c(PY@A)rdA z#H*U|LPXzE9wwQvmLJLtZP0ypyQD}PdjeEDM`DD0lcXmkxg|m*w;{=Gcp|r9CAVQM zx0^iGXt|f6T)1Rhl{Ow1bkHUo<$36}JggjcZt&U%GB31o+%G*2E6J-RalUM4?6nz% zq7s7zQJJxv^7-G4PhDMCMp!Fnj1$b3`DVJpHv^x^+h$~Uw6e~WiF=Lo`gksY_5I=B z7FMG!F?v&_*~^PeCem?R(tUxN>?zHk0&s!YG-B2vt1_mp1<)mf+YtW|A3XMCk4lU@? ztixy;(BK07zMzjS`VW4G-iwWkzry7|Y1V@G>DeYbfO~L_p1o=uz;*b5W(+=r8)Ws- z)T)W**VJe?7odH+01b%f#|<=22=m--FF>O^0ol%STY*pLT?_7)QRLOEf~c6^V~KG$zDI#Kh(rV+UtHy#-^zuDwrnbEli9WQp; ze8t;xB4^#@3!dl4^0|o7`I_{|HNB>>)(L`;N6}|y=s8|9!o&NwOt>I4yMBA!-!yJ> zkB9KX#?5Kh@iv!3zkS~f;pP%x;>V1twK3gG6x5&;$_r~w(=+2v2=wpvuCa1;0y<`A z8;OmL@_bqS&FFA25QAIeqCK?R2?Rp0v}Yxi*?H(WP;opbo@aEWq-Hw~k>v95%FIJX zmv- zf#b>&)Pm4QO}3*stypNZxE4PSxF!_nq9*nF`Xg?|nq`Wj)z-PTqeRzMV$F#($FrRf zBJN|{+TbWe3kVyiN?d>0!982kE!T`9ZNqVS^gK~)4q#zGU|O4IlWUjYxsJ{OgU~s2 zbPm1OIds`M^rUlK(rlkcn((=8##T#<5gNW7z)@8lHkvNlk}d<5S$PrzryclVEMvW` z%lIZ48IJXyQwFtq+#9J5c^t8>#lB|B zOv5? zcF5rks%pw2Gf|_mnZC%Q*a1}l5lV^Joj4AvuEV`i14Z?8LH4V@_{{~&0x|N#5dg(P zeG!!J0fr3>dBCHN8%vhtSRcJBVAN?{zmn+g4I)9i>(94WYji85u z3?2g|=c}FeIu9EnVDd%7GTnz}=!om?VlHkuc%!&GE^AfGIw?}H%F{JpMFKhOqVdVW zR2nLDqpXm8yoWS-@6BehG&3%$%Igo6W_l2ej`jd#e`RzMD18Pk7ZHdH!>g66JvD;n zPb*LDZ0_`oSO1hrqEJvKosM>N|yS~Bb+tS|q@3qXo~%E)&`+<8BSr0d>wll@{h}@-(8HrzJ**lo^<(+u+B; zJv2`%11F=B&*bSYqvL(Dmo$?+-5Xdb zx9Dv+C9?B$=WdL^sAt4@hu-Zu_ufE|d-;7I1~3wzJ8|vjcWwmqSDB!vs6rof-4k`8 shOeKu`4RfR&ZIv_AHm`&TIgo(R?oRA*6-u*DC%($^?HXsrW&pO11YNClmGw# literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class new file mode 100644 index 0000000000000000000000000000000000000000..ff335f3f9aca3d71991863cba9f04d70e52649c3 GIT binary patch literal 7666 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 age3Rrr3Me^J_hp@^dUSUyH1`*Q2POuiP@?E literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class new file mode 100644 index 0000000000000000000000000000000000000000..d999b0e27d35458f7268c14f845cff3a208ded56 GIT binary patch literal 12231 zcmeHNTXWk)6h7;w@ug|grWaaTs0am;0y30xi%lT+rZ{a#oRo5{B5fQsvZP8Yw|V9b zhGB+(z$-62@IVJ<;GN&VpWvNgI4jADTq~C1k<*arLu^Y~edp}Ce7k#e{`~jX-vM9- zYFWq-xNci=u`a24@vhX6in38J+LBq6HN#Pgwz64NBqr4jg=$5E|154$&7@XwR&%J% zuDKVplFSTiD+_%Dj=M0Lq}Pf!*X~l85jbK~tD(wtQDPeehAP`5mYJpMwGx5zOODRe zI-S=Ws;#bRbVAn+CNb5}Z36kq4MW!*O~a9c_f@vB90t5I(vCPC4vrJ3ujm%F4Xr^H0qtz6nma+kv<#ea-5wJPd4HmU zEO7$}Yg7mxVTK?HY|EsDyl1!0g9>mIh6dmyoFdR)R&|w)6F41Zt5wvuqU!X9Q(vQ2 zRYGOBn+#dfRwYa2*Z$4CMI~i&>qELlfQ6x^G|9G0?S7~%NM-rSXz~yKBL#O5KAfUmh=opvOX% z^s=Zl-e&G{pw;*emM@23{8S6wX#%I{pQLun!39F})Y8F?zRuZ@VqztP!pM(NDM=RR zJujtzM4*ptsK}<3G}6SUd`!qb&Ittcw)l()%2b$O3FVat`KAZS&!LmX@IKD{)XbdJ z7CCsHz+eB@d`^;wxQ5CY=nPlJAHtE|q}49B8^HpQ4hc`WDq?a>_aHeu@Et=U<<+od zz4NUcEO@B|cc$#kU#D)Y_5^b4jxO_JCV_89+?rUZ4%0kBY?loE zYTB&-P^mJh2ES9;`UYqubF~LsvL<|6s(Qr?ETb(^+tFAROKASBh>Dj#CgMMMHo)pi z!s=GMrOVZ(URX#Abg!%|&6g_I7&BKab&1|{sLfE{kT1&JZm+||#X#PpCC8>tAzM|Z zQ3n;PpBPDD8>Si)Nt~9063Kkkc~iQJa`s-9%F-*aCp@dASDb8yN=z{j<2GhQYf#4w zPG+!ehKhj;g>cNsWQW|Lb)O}d@lBFO7Y!TVINeW7vZg?Hkf+6YbmI$1DG=|)CzD{7e z^Hg}XFHyUV0ea+Y`&QYCOSm01fIk-vs zlaNYvgSs6N0zW&6yy-;r7KsQei^4n;R9jGWMYZr9zP5#}73y}Z+>w6CV zPZ30!_cI9Vsz&WXe($6x6a{kyOiA99YE&4;J`oZ7aZn-b#}Tm~M-2LLM7JMD^!jmz z1;wDYzPS4U2iU)+0HpeVoHoEy5Z5 zY2X9V?X~fGaoYQC+Em@mc*ClR5;_x|5rf=|k=(yE!@zBEob=s|#DI(g^m1C9kY&vJ z3T@EXDDa=)CvBdC6IRWh!tO6-IlQ?nQXUWDh<^y7PO162sj0Hc1lz%`HEg@uM)a6s zWR#PFqbqbBTc8wCSa6V}F>~C--O@Y~YT?3W`@ST-C>rnAWU1kSb;X}ktUmo*8TtL$JuT-Ev|H{!Ff zf_U49;w;?4KFHA7Hw(A1EI8|*U9<2hfn%-5@+^Fg%{6?X&BB)i4&kz&1qpxP`92GB zOOK(u$Ik+)9csVyB7wlbf@<5i=vJ|H&;|hz0{cI4;f2G{kKb9m;@>&^9>mW)6d(gf z@G49E+XHxU86P9~UHA=--X0qK1y1~dR~a~spZsw^=rise{D7z6ERH|tUO(-Eo#c;m z8R1V3d=&@w;kO5TJ_2~u1s-sP6yO!{y5fj~tn2ZqdPo#+HkcgSx%wo1(1nG+uajjbs^AREjBSm00HTt5- zm<#C5HlA$F-W%=E7%H46Tj}f&EaJTkd=!8$;q^NHok2RT!YX_WpTKAE1>Au(R6+R< D+7M1X literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class new file mode 100644 index 0000000000000000000000000000000000000000..648174f547e6a38120c19b493aa79b0b70696737 GIT binary patch literal 11111 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class new file mode 100644 index 0000000000000000000000000000000000000000..a16ffe7978ced894f567f2f22b26042e008070ca GIT binary patch literal 12084 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 Ir-ta&KZX3sNB{r; literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class new file mode 100644 index 0000000000000000000000000000000000000000..ed3dc5b3aa453dc812009dea4ae1fab4189e2abd GIT binary patch literal 10098 zcmeHNZFAd15Z+7D+HumCwDg6xQ1t~!Qfe0neRD|3dtDSK#7UTTnCV=kv#nOrnUj<@ zeC40Ok6;EE_{InR1|Rte%rNXpaje8@bWJ=RciIn@bk^RpdwaWkd#n5Fzh8a_fLU0} zKo0@k6Gp+ImRop2H)z4&PQjy%f?@N(EO_SAf=PYqaFf{uE*}eZW;d89EY4tK*7Yra zv%-W$ZR>j`GSElhcnD-uw^mqOf5Hr(K(Ebf1WxE&)C!(m9eYyHd9B7ok-+)o06`p9 zayKl`TDRGx>vEs^7I!@Y7xWfbC1^AR^Sp1Ua4omyVdp~|)DDtCre&EIS=O#A-RtT=g1K^uM`*p;#C zysF)si1Kw$x{)MyEYFicBkZ(58@YZ3kBRm}o$}&N+WISZR5ga(Z^+7%piG!<&t2mh zH#_GVF(36g`qN@n*+Fk9N}=R^3PE3KiH(hCA2H{);*eroURG;&6j`V_YL`;wUK2x;3~H}YEO=pi>(4&FZegve+|g2+ERA{1 z!dg^ko8w}&fn^r96kttleR#k8a3PBTi+U@H7Dw83K%l>3)m$nUI|9!R&TxEmBF=MM z-?=gtCte_hzGXzHl&ibzg-cXWT*8@{j28}bdNbcHX5lI!KPpu1)GSpg#Hv9xMNC=t zTa%PRxKo=c#LBCVLflJbtgW*&Zz+)@A8wJnIY zcEyoLS@?{Q;|iYmMr{TOTvH2XGtBmB7H$%l-&=?BJG#=!OW>jrW{FEOn~1c&9XmsI z1in2;mbW$addu-K-HSB{JU%#lxHXD_?&5GpFo$ojHHIq!cS|P2Llaet3EZR($7PY* z)}e@Y)@k^glS0r<+?<~91>L~Tti^)B@Q$wfl(mH4_$>ny?jkm(@$*{X;2{l(X}yqi zJY+wMM;}n8V*4f%cYH0Zh`6cH*71sHQziF*$SYUjfiTz{o^l{?F7cpAcus&oMh`z5 z7Bet|-CAriFh^h@Hl7UJM}k(0mVpNZj_%yHWI&gPM(|5X2Jqa-fYj91urjcO-CL^5 z!19iBE|K^dSiygHpK&2DxDZu@SMWrL2R8r@n&1JPUN{Gb;4ro_*pl}g-p|1R42Jh3 z;e7y~kHRs0dmNv}uouC5?pGLkc;e*GaOw|i^}t!YN(8{)yLcUps1WTuwz6;m-h)1P zAD?^R;x<1G+n4ZnqzB1mgTQ6HN=Bo|s0Z&6qbmuF?uLvcO%^_Y54YhzYJ<;^E%+vI zYXrjyz{@e(nO@jYB27ecdXgboa_L%r2kbh$!)7eo#cp!%O(9WFp^xVGhoF6|we*=I4JZnLN!G^PGoC~SrLvbwP)a0=fMVu;A zX0gkT;!Jpvn+#QK!f$+lsz4j>XiN-kL#dBiFwJ1Ddar~Y$2+4#Og)2kKUGpozs_K4 zWp$6i^adGfL5IQYnfAb1W7}|>HQMjAMQ7A8W}E^kq!L~l%?oQr3Ujx{yPf0!xj!wC z2Ln-X7fsypRA_BHA0hv3B;s41OUI?oq@{?|DP8TOMV*p|eQR=(Ka_Nr@hDZoIleCy zy8r1VLvkDfC`5WB#>kf`^N1jzfCz$!Ac+1z5LFOF69|@hX3+7Fb1r;(z&(`)PYO!( zyg9-*3iw7z(Cs9@32_lzk-R*1}txKh@z=3 zW*Id5HH8*ehG(SN^;YU}FOX`MD2Ur-K0=!a;t7L=p-F|>6IKSfK5P=S9xa29E5m=} z{NSV@YB{3hrYceUS5+(YR&UrIgU+rTYvD^v$$Y)xaQjJUSgKWAOZM%{Rh|90{`vPrAFNv?*>`{a37(QX IO}A6O0My~x1ONa4 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class new file mode 100644 index 0000000000000000000000000000000000000000..70b98be695bebd8bfdbaa06262b34421415ccf24 GIT binary patch literal 943 zcmbtT!EO^V5FMu>+iXKq0xc9SR!^-|d4JK$Hv+QDWB}E{x_F{BK;AHqe+(!hO zFZ+?Fb*zjECj@r(hqt*#!6`F48XR0A@C*w~phKX03+Ov%-8yU%^6{;4(p!BabBY2> zrFl@+(9+q7mW6*#)Ao2SX;_q!MgkVqu%#L*xG^@+iKM?t7##$vJXI!F4h8E4UQMNw zMqq}ivjxWQE4<4o6PkNZr%Fq|%#uqyidQ8U*L)`F9@fOrbcCYm3{7W$OlP&}Y-#!) z&8_q_R#WgG<}_f@@y`dFcUvj5n{Ohn8=E@UDN49c8zCzP6s7JLn1NjFgrdB>k6J9In zxD)e50=on@S4Icb66l;O?{UV)I2Qi&Dd1e=kJC8ag*(s!z%_|0o;R@gHm)f~&kz-H v=@-~|*Sq@(y5BHrKo8$7#64~1P`%&x%2(*<9Gh?^Yd2#*no!~)Cjz{CJs|3RJhL$Ss3dyv@8o_ zsq-YX$w8Pf$5Nee8S3H~PPojt35P+NQ&S$DT0y|K}Cu+nrLomk#oviJfRLt z!;dmdbk-e`A}AOalm)x6^Qs0S&9rf4t&f7TzEwy5thL{qws}q;sMC~{UGtk2f&Omf zgAYqBxB?4Elz~uuD^Ew`0s WN_AMpwFbB0PPyNO2e>!z*1cb<0r~v^ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class new file mode 100644 index 0000000000000000000000000000000000000000..05d457ca757e19375a21c1807c4ebebb9b32c48f GIT binary patch literal 3158 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 JrF#@m_&-z`vzPz? literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..d10456584a8af7c5430687db32483235ffed881a GIT binary patch literal 3311 zcmcguUsD`K5bpuPaR-J36Er58DU#?NCc8o3a#v>|0YlY+CU?Q|!G~dYxSO!MvzeJi z_z1rG?&q*dtMuIuW!bZP9J?H;+Y-tLZkd_x{`LRvzyJCDPXO42wF*oTc&?S_hD=26 z8+OE8Plm2$vFimg@m=j7x;`^3ls*q!Y5#QxJczk+_xx}(GQym^HX^9N41pV|fPh7P z_uKt9+%p8`c}lVoFs%vP?3_`gS4{%duINY1B#INbcmBQA%OHBqHj>Uq{HN_m8y0y; z{Jh@iTvl}}wB8tj5V-p)L4`xU9UTcR_5;2iMba=MWQ6)xJG(N9k|037nGJMF1#S>n zJTq!jDXHdQiI87kM~Z719C4qbOJfnFLTIccN)7chTB{C)QIA;=y3gr|#YoDiIf;3# znmTF%P39FNQjE=GkH=QyNlZb&`z%=ZJg#+Frd`=hyul7P1L>FLL1#)YRjtQVpPRCL z-GNL3e^;6pERK0pmaT-tYOn~m7T`846PRs@NSHQ(+x1-fb&V2HzuDNs_;y6Zcaw0R zs~*GP5m@S=J;5GR!k&khGiD$#N474Vl(wEQ!Iq04m;VRc8=HMYAZ%Y*ad7uf!dUd_x>Lm3BoTB~M31~5W z>J56JWJnDX*d|8Da-!w2*!KJfYft0>{p#`Q@r&Z|%kW5ka2;?)>wuH415UOMtWaNaP3;^Dnehf`p!ji&hUUfLz;^7e;iL{L zEj;sq2xF-X9r^kKCyx;xJLz!EhVS^GEzl3unItw#7w6S5SnH~V4DL;PLHsNfEKgr~cOn|pYw!pHCl z%nWhv7jRbay+U%Js|C;pL(r)l=+hK*3K?;af=43*J{<}E4Bt)-uymOL^$QFjr3{#X ZRg^LXpASuVJVJVc_fPR}7GE8h{uke{6iNU9 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..19c45a24a1a651c8522c070dee1f6fab11c3610e GIT binary patch literal 6797 zcmeHMS##V(5bkj{*&`+nb`p++pd5e=L?TrLLhLd)j-70=KFSvbZ)0h^JBg%GW@fkS zpTGmZhbpLocYX{%fuctv+ge-6DvK*cfgiM^qo;eid#1nc-ar2O^tOU%i3OZ4#G329DpUx3 zGdwHHg}}KP9&OZ4x=G;WhU33{P2wmo) z%iIH(xv9%s>$00pAeeHD&Jopq$I)mSwws`$d3NYT*X##3HCSop)&@LEgw)p7Tojmi zSh8I=^_KPTanJR!6|(J8G!LqGVw9O31~EJ(V?m!UPSzlyh_=Nucj0-LhH5Sjo>0{> zA)?SNX8#s^aI|n}8WI|H%WnXY;j>$U=CzRghgUqnekPDdOm_svdK#x&VA4kNDI1zi zI7=gaKQztwG|^qjZr!~jcAT$gZAe#U8<-gpSBl4NJJa%O)XQwJ{61P1aoi2V0U9*p zq^sApWHcH;x$+2+c460NNs4F3qgY;eO1y?R*p)t8rMgpw`2c!*hBI@wnYQ>mFTxPf z0wH#?z~+`_#ts`VwKEz4ZQiI`J<|;0TeerQ?xX8Ksv^2;p(Ik_*Duoulc(K=d zRC0=mL`J`c(9e`!V>EcUSG?Q;x4hDv+#W}xuQS=>)}@O=E;7XIBISp*`zz@G2=(Ui zi@-Opb-xC=c_*Ynj$^-D%AS@cZs9f!*EQ9w?3^cBC-Wg^aZ%Ek*M2U|Iu6P2&G|l4 zO31>kogLh4hp29-AG4TA?iWz!r5OI!=>rJ&BSRu$Ra)8RN?EgP#;Ti`e2)bySQ4b$ zqwL&amezn8yfF%A;R1onlP!bGURn{@a58NwhOrip*>X49W^%)vT>|4R;nQ#{JImS2 zitcbl;BjmCk|kIDOcS`$F9i#ZOw!uPh?v>P;_CD)O1rpYf_igD!>22WwIFHt>(!AAZ63WL}s!%P$l;3Ft#li=y89|&B^f#XV#Ny!6-h;S|U zdL5w~`(ElhDr3yW6@EV3qhU8iOj~7oX*z;=Qh*eY85gGZZ1dg0m?ngV5`Sh=Zbxww)bgyAR8k(yK{%w^zf!h9(YVa|E8!z9T z*ign9oviDWcBz|~XsxHE_ae4}2~6~NAJQ%=CR6LYjr9|@AqepOI`u*BIA(`;_8(-6 zS>n-`C{DDFwKDK5*D)auF#s@$d1D}!h2B)`G1GXMF;%=r;CwEVtiqR=a2_VxcBKSH zmu%-`19M$fxdCVm{96GqJ;z7_=kT|RPmB1L;Mn*TYTu7HeumNC@#zeV;jf_qye{JJ zsHK88VH}?(`rcehsV>76oc9*qSCIB?co**shpRXqmvpT_(*2T>uEYB%$?$K$2sk+2 z!s|9(Re}OPFgJJ^)kbi%s(n}>@OmkM6_`8_XcQ_TY-(s>1U`XJaoI<;+gIq&SHsJw dz#TNp2)N*-<7WrHx{J^E@v5QeK1UyZ@i)c`tepS= literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class new file mode 100644 index 0000000000000000000000000000000000000000..8f779e5a8407fef937dd0d8860681031859b3b39 GIT binary patch literal 12571 zcmeHNTXWmS6+Wxb=B1LQ$WGcujsrVsB04qMrgfcK8YPM%$x0~6wP@wiD+De{SRg9p^C?GNZoJCnZm2lO9wrtf|1Os3yr0T2`+VQn(5I+F*GxE!4C-1eN^ zv-taGfBBS%R_WJynqc&%=Ni?HZrarc`hi|GoKDr#yH&$-dX1{r*snHpU+*{#ZdDy| zT5WTy%iZd3qqDvDfE#|Ea*Sqzmo43HRpnhqlSa?;oz9xct%k?wTy0bDE-)(Ym@QlP zdoE}6s<^jU>%Y6>yQbY*NZgU!7MAel+`XRdn;pJkADEuGXYpm*c6{A89UJ^7YwviD zz0tBAmxK5jqYYtrW7{=brmb6%7{%I_W7|E;0?qkF(>DDjMo-O`cNyhYpkl^}*HGKQ{c4TpQ0uuDhx zjke~)eEhHrV)+PS5raoS}6*hCBnl>5GOLShGS+()JLHg zmTT&s&)vgLrX9`@am#U97SD2=5LlfO;iW+^=cSU1fKPdl9DxcXCuX1|pA)4>5+Pe>h%+WOB9C^s-*y^+mptdon$wiYH7-4i zQnmV$)42$yvk(Aj`W zGZ-Z`#Y`|8PfhL}o|Q|L)gkRBf~9fM#z>v#DulRg{dZ)8r4p( z+r{eNdrx<5%vHP$qdX${7MdEON@j3X6IG2W+aYRvzMzU4Dr%O~Xg);)c;|%`tRqH=J#SH*U;Y1O zZiL35`9jkn>R`GXFdfcPOqU2V>5IS>(?MT$IVuD&?<fI@VE%DO*DCt$ z{!!36PwB}Er9W8=p4M*<+hl1Ru>E0Omq3WA=&Zb3pw}3!XPZlDq{8{=btJOJ390i# zK#oJTg)%tpjDu6(NY|GFt``Q!FQb=JqPRTlGDfA zqg$ssUvw#|!H!T=bRx$wx}3m1KFtB@xdYwm@dCZc=x(-mk2A;`4>wbcQLCdEtvEJ1 zh!~y=skUjItnLZ%KV*VIn8nB-by2m-(G{cj#}O6jN_>2>5}(_EjWsLK8lyYe9*y#0 zmM8OwbKy8f7g3e^)?N|2Wr(9I!*}~*MQkaa2ZML{5vB%WzYS%LQDHvVs1(Vq5}Ybl zI+2aG8Leb%mpOZbI=%y{?Pekyp$K1sw3tpDzdaQaM_Zg?^ao_ojk%CuOFuAM!Pe4U z*AXpoQ_PAkN95^`&jl}IhZkfka!)nld&xzIUCbYo_c4vc8fS>M5cTn)#(-DC)wcCo&Um{$dYjY&(AA3U1-!GrEU^8W?TL6{yYV>gj=x^$6aGLM-^0W&;z+9jE6S zd<|c$U}7Eb;sxJupt43gw`uE!WE}{;FhZg))uMrpi|K~5<@onKz8>`XFsLuqhW4pI zHzSvx@otoOwJAPF+7;gqnHJVIvaVsum%KG=dXjVU<_v1uF}kM>m+R=<_sQ~f2m>Gm z=jo?7K^4x^$Bd>1W|w*TIRrU2TglTe2fm_`xgMW2ZOV;J!NJR2C2+;ZTNCtKI!EX6 zpAW7IRK)*zJT2k>cl4#9XC=^zXOlSQKsQAf@H>sGHE?A3Fa4EfKA4^TGhO-{uAZVV z<5zGb9ACljq@<#+(sQ_)8@O{hpn9IJ;5{kP3$a83aTrHS!FgQ~=Ni2j4athXpBGx>WffZTzIxt?vF*N~W2*ntE9lw!!!Zp&2H&P()DMMCc^}eA7_stYI zt_b%n`Zla5w92zkt4N=TSf4b2kCXv!#`=8cae#iK2y}~H$Ef%&gw8>c+hHUw#@-XI z%Fy$T6hr<&37+T{EvLZ0H3Xjsp5)l*2Tz(1e^e3zYjRpi5#sG3Ax`o2YKjd$Re{HM zpzCxePD&dws8BOU?+guFkzSG}=%=Byc>9Nn48VDpYOxHP@!0Ig@>Ys||5W6CcSIfj zt%&ozM@Xe%A}>cJr=JB=!kRUdV9@eDVRn-;^qHr~^F#VRL>DuG3n*tl!2b_% z{3s!NKYjf;CB;0ih@;b9IO7nr8i6A);<{xd0Q)(ukpg%{2{1>^7*OP61+dm*gE2Dc zL1@LKc9Hk{7JPh+9PPtI6J&+;zZ3h+#{Djihd6$PV+Ir-VV-bAKci1z^I!ZI%|5t7 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class new file mode 100644 index 0000000000000000000000000000000000000000..1ba22e911e1bb885fcbee492d8a8486ad16e0017 GIT binary patch literal 8751 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 P!wz)eF@zAIRLTDU6vm2= literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class new file mode 100644 index 0000000000000000000000000000000000000000..b8905004ac283e73a7240735e2361f96a30e69d8 GIT binary patch literal 3499 zcmcgvZEqVz5T13NI6eYtowTJdz&2{Z=H-sJPo%1phEhekHYf?H__)5?*xTIg9=m&1 z`+N8iNFc#?{t^;m_U?SOi5nX=;lsVHXJ?+7otbC+=ik5n4ginfs}?i}>>C{gDHAey z&PFVVR2mqT1yQ2%I56=^5HrhC74sxe?llCHE;raVo z!`OO&HgE0c(u$Pt%8@XlpYUxdl`UW*w3YBNx0Y)ZTpFRNENY9pXAtIFl&l! zSSCV_)Cs2y(`q=a!D}zz?gAJW53pn6@qkfR|7_|`og0{^IJR(Ui&7909T2V-SRYT3 zMOY{0Y%r;!W@CTGVAee~u=Kq4+4)yu9#w?9=wLKIY&ZZPs9Z<gZch484|FZ=-u(l=8gLu$P8Gm)cn|NZ z#k&XZ!w2{U2;&a!EDdiKCOq8b4By3Ttl8}^HQ$?|d3}av3?zI6%}R5(2Jf`a0pFWp bg3K^+2|ht(4fwQH{uzFMiPti&`tbQbyY*k1 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class new file mode 100644 index 0000000000000000000000000000000000000000..82faaea9e1177035c8afa7fea71533f15fc9dfea GIT binary patch literal 3853 zcmd^CTW{P%6h5;}yBAUdX$rjo)0E3@Dr_!&VxvlFdW+-|sR>00@i1%8?oMORxHIDw z{R#XcB#_{pAB8w$@5R+_;*FFRBp%l5<2m!4`&pn^=7a2EpcgW;`A8@;m z>&yxz>##!Log6pDHGjYNoQIacyWxa&S4op4ToY&ollLxx=7AVUW;4wRbfyd2!Tx>J zU@R^d?H57~x81wAZ?zw1T-wLEiUckN4@>ktW<-cgSLVNNZ*6V4{TH@{6!tCw+MY`7 z&a-zr&j{R~u~aEGk5CA7FxQ`E(u#!d$&oOk7xNt{mCfB}2;2x}-aE+BRC8m#XIct5 zFsSeO5kmtBtOv15dg{o3#HI5+k8p8i7o%2(%LLYDAP=-wx&iN#`Y+!}L{ssIM--!; ziZ~ZaQ>{>o#C$_r&BGz5_GQYc1Mpl7y&{G*WKK=m<8%Vz(LGCrp+ZJNW0>QYxYXyG z%Mc~>wMvkFk>OfIy)b6R(7uSdd6QBij)I*6DGQI-fYVL1+{1iFg)rYe%y(~^?_Qqo zUXkx!p6{D9Qru8ydEzpkT67TqIYo2Jpjc9#D=9B`+bKm9{t!v3v@QIeSrIRA_$!QK zA!o!^WRtfOhL~>wAMr~0;~kH88S%c5Jo4z1%vhId z`8KRAAxG+nQ---Xd=;V9T++yC@2pVvw8kwT*K+v&;}Je9^-k-|e>s-Nw)=AEj;;DK z)0;UQz+n}0t8BfiiO&24jrnzL(f4AqZa|yB&6Cp!2VA^%9H>l( z{2@-F1a6&toyo^J0#}1$4meb)5fr_!TL%xRNs`s!3%vD|t;#PWb@&P|0COoeyVPJw zJrTxW6F9^v+yDgv=kW4YgLOC$7m%vwR0HWIw5If1$c201BK{W~Z^IgLx*hBIe+hqg zaLr-0eus@8*5CUTuKbBq4X)$U@d5sB;Bz&9Z@~xfAyNPzp%xR=n+s9h-U4-V0qVtt ms2{^8Q`Ao@P(ND|^&E6ilNxMI@oppiHU1hX>vQno%YOiKve)qd literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class new file mode 100644 index 0000000000000000000000000000000000000000..fda83cd23e0672483ef546e7e63fac0533927c74 GIT binary patch literal 1121 zcmcgr&rcLF6n@2(-5pV25mdZ1#KY>La~ERbk2N7#!N5x5u{-mCLZ=hB00!{54RZ|kyeslbDwChciOdUI0#< zzC6uK8DwQk)VYn<{1|nOE+5NK9Otj%xrb*B`mjrG8I(0`Xfaqy>9jOQ`R@1xi@>0> zRcK{Yc){RS|LFmP)@#b!hAxA}tLTAq)^*?xn;U#E4!zYAEIIX6E1lBw+S#$j%0K5> zcXW(AT-3;80ujrJX*pg7f+`$3TakU%&|Aqx zp{4iyP-*o4vr36nI5tqr;#3|X-=N6_F+vRyBZ3$azZemT5iw;%Bt|xPY0>jIc@??g zfdInun#TP-DUmKB=_0}Qk0qvfsH(OuB;3zM!g|Wib=?1mpVhj7o~*lLjcK&L;f)8& zWgr_cCfU z;pQ21hcf^+8D#smaRt65{V`a-am^BUZFt0B`C3^93-6ToG}efg!_NcI&e7Hb%n=;7 zVV>+ZS6?K+Da+xEQpeZrq7Jc^%i=1eW| zc+T@mG;GkLzG60LY|~4)A1_P#22Io!wXq;RYfyR3-SSzSgxsKu2LSHQ%*@;aR4G&( z3nZ%#Ae1HnUQaT|Sr5`@^NH&@JT&M+bswyhe0F-npsP<3KX#jZ#ouuwx9;&d-w$FI zyMZ4Wbgm}#>M?)IUftng=sFxUraC8m&qCJZF%S36F4rChzMps=rVQVQ6-qQ|(0Ci= zLKp_&D4jQG`3FDbQQ+-x$AlGIt|wVATVVj6ns>}fIiqhjm|)SYb8~M>9GJ|EU$l6o zLKAdxj4sh-gGTPUz8l{&=zLX^V=WF{e@hBKUUPl^IBC{-xXwUFs5EFWZ-a%d7^e@1 z<82qp*r@f@CsJ>NZpOEGth)pBUY$N*e*j+2d7Qb$HC@D{)q-xKA6e)_)(BHi?=U!= z5YP*n_28Mk#C;yJm^-th^nr2e*X{<0gaw4*x@&)MAiBQ9^69}>=R)ZLu5{D`XnmWT zaQf}QG2=jaRdw17Hl-t)ox#OY!NqJQQEUe3S~7#sbek=BIAQ}_I7$$mgdeAfE9F-! z=TWiOpYlfBLoG*C^$}SpWSK%h{qg1_N?VAt4Y5?aYuTU*f<_wURlMlr1nYX*p!d43 zYbT+h&I(3_(DWul<7Y}*rllcTY z4JjwQ48@G=0>bLFvuT~Ab%pdtVjZIgRV z`ESYc-%{njRTqW-b<+uWWQtO$ zb_mT`3;W_)M6nKMsmUyU!+R&EyNK|(U%4EI?cT$KopyO3XYmpLt zl~iq2$=r{mBhzppfi*rlx}icr1Z{iKD6O(lp+Zcq*c|T?88+=V`_%CaT6`f{55bNh zO9i+tWY_(o9VI>Y@H0I@wyT~aDf$?3vz^A*yyzDnR*UYd2Q&=A1%Pguficrpmr-5AYjPSzyz8!PS$8}U7z@qGHp#l99+<@n5c>amSk~@kUE*u< zwTz7(DOGp3qEiZa8x9q?Y(F{<6;#PQJN+|GRC4ypa0VjI6R;)sxiKDF2J>SoLt&0>;$LFpv)EzsXVou1{KoZrj2I6sUQkR^T7lAQ44tZjpP^ zA{?@7r4^z$mC1rFZZkC-bgDYNg2SJ+APF0M5l0h9M0))%iYvKQi4O&8vK+zT?_}*j zwTY)?oOF9@;=~c3C#?854!$oAJUEWJN0!nrA?6G^qwl{8^d-7)&{;jjN(M@_0AT&G z0$##X;Z(Nzia>4&l?Xy;Pt`#a*m$JQEY@)Z3N}U`un zNsdM_E>i{fqj>fPjRERSj0D76_#ekrGB7xUo3prX17HaMl|N|Wr-^gF(S^S-8lo%s z6_bcA(^dSA$@>ajqqi|K=pA|&GZj&DkHq_Umthri71pU7tP{CdKcpL=oiUt`ty&n{ z41JVhdkee@@o(aKS*(}n<2}i(TwaA?l;m~}uc(OdzEhu{(5JvEB(H#H!K>hN&s?8j zkSVrpuG=~2Cv(sb(-(W_g;5Kj&eL)pYJz>% literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class new file mode 100644 index 0000000000000000000000000000000000000000..b6230206b22d1fbb887c9e3f5db34e6c395218b0 GIT binary patch literal 8147 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..69032c20370295d47981a3cdb662cbb840f91b0d GIT binary patch literal 3825 zcmd5QrV$}sT@ugPF?l99bFj8Z2V9QKDX*RjAw@q@_n}mU1 z=8VqhcYl=Qb8kY}WhuK3RAq*R+vnk5<;rdQSCbJRc4d-snwtomw#6!egsj?#Oc_yMW0zM#k z=onH88V3pLlP`@LruP3VCghpcKg=_^7Ri+RNaHgkSSFcovbr|?L?%fdofX_(sg^pr z6g|6Pyq;tt8>Y<@X?7`q8&S@a>|4`Hwz-cwAE?@1Kc6OP-qmTx!!0x)lSk`^0q>f= zaQ?faMRL>#jHa#Ey+=pi@nLa&_5O`!_l`*RBfjf{ov_!3jXViP2=%LXq;_cP}S4xD#x`3W?G0!>@1)>{AeTm7vV(#@#UKZylOnIzSDuynNQHdHuM?vjy zWrk&;t5%~ax;jIz)2v04&2ls^)V-5oypt)ULPHPhSx+SVNglVkYU##h(VQm(Kic(` z(9h-J_&(R8zP`ESp(`~kn%)uniJ#>PZ~RyuH@(Z-Hr6^A`h16Hja8uC+Ro9yV!oLi zh*ZF=hk6SzjUt6DUYY-pyb42g%fZ%?M=<$j-Rn(tebrd&d43NU&i9+4K#*C!mMew1Ud5GS8x!??sxTEw!-sr;`>z3}0l3Y_zj0G>K;$Ps`GzNQB>{x_t2i<1BV literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class new file mode 100644 index 0000000000000000000000000000000000000000..2916d066fad3e3b6e8dc720f77034f70213aa6e0 GIT binary patch literal 1217 zcmcgsOHUL*5UyE~eTagqD2k5`C*aU?mko&$Oh`sdSS0b-J5%g7^mLEiJsbE5{vs1i z^zM%`R`)Is!^Xsmhv`h!eErqqtD2v`zJ3RQU3l4qMgo0j^RyIFrzc`8(%h7(6IGfk zQx~Z#PSZkoQJMl(YQk$eLRFzn2g2v0^i4Pq&{ak|+!+Zy#6J2a%q6fC`4qX*_k$D6 zeFCjsuB4WJJAqs48%GJu?GSMj+6gRPp?9q{wgq>R#^(>(qBClY1*60&sf3qCv&x!* z!qUBBo%Z1fnLn?P1p*$z@kuzA3$2Z317sH@ob7og9h16{mNHW3^ywHa>YO4RTT_z# zsitoQfu(H>cvcI+^@?4+CxEunm{)*xll*K%3xnOhv;`K3eqK}Akoo^tZ!r??X3jbeL2*^ z*OmxYrtV&LnQ(7&duy9us|Pg|P~x5*ODBo`br}2DVd86N(^e6((>Jxv@hy!sU7NkS zaUPnmO1l}-Xu@;ixQv;=!aM046?#Z3=jIcjO{Sj-K!X}|8|KMwlC?SYcD_ODW4H4K r7Jfv`5}g49^xTi0@Tcg)1G@h(+RN~0ig_IEm3TgZ7tz~*r||4Ix-oW` literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class new file mode 100644 index 0000000000000000000000000000000000000000..34f550f8e7345f13bec8f75d6d4de405a5d307a2 GIT binary patch literal 1080 zcmcgrOK%e~5FWQ_lPrZkXv?D_71WvsZdC8T@FZqP$!j`$`RW7ux zi`*AidBFoOZ6S4T<8OW;btPRs$a)v};Lr4TeBG?}ySR#T=iC~F{U0PV_X`H-_l+b_x z$?FQoy_*!dW|3=_VEe}sQ#@2zSr-!S*DPT@WoI7utbJyUskQzKCp1{f6N6a>Ex5k| z4`7wRa(5<_txoS&8vWpu86QUJtoD?VXLUK0Zop9u1U7nBaDC35iuTO8lt3Y{+1b03 zXR44x0`0yU8y;$h_qRKHgA-j#6J94_Cq)l1Xy;vRf-2>ynJBL?_lqd3G_1!Iz1eUW zne}b$ggimf5Lmyv5(#Gl&%5(1y>ns*Ucd853?@dv1Ozf1s^2)7qt0byH$Wwe`U z?a#c~7ifLh$Uec!w-mF6?-&F4tS3)g(hYcs_cxQh1>3WjNBCWY$I1I-cKQ@{&~C)4 FzW``eM%e%W literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class new file mode 100644 index 0000000000000000000000000000000000000000..0c47d77f6a6ec6f0088937d64d6df9f0bd5a9405 GIT binary patch literal 6351 zcmdT|>r)&>5bpt!dx%LSA!3L+Qa-q-yYa0##3TeP%1dGeL{0f+*d4eDyE~VeS%kmL zDy`D*{uBNumOZoFW`Pa27ROnB*t>o7{JN*Rr(b{m{p;@luna3T7$fjltH6($Nc<;k zm-&H=ea(7)5Xm(3b-3e)%&=I7Jo2Tz^}9Ukapi9^6LkH1e2b-#xoaODaov-N=1X0c zZ1Xj4YA`|Il7o%#mH%+_2@mk?c>-&v;#$s*z~#WP*4#Wq5t$Jv4uSKXj?kt}ps^;l z6J}Dy32^svp)(9Qo~B%zeAhYa_9F6}$wgXDj4=Dldq%OqNJXI8SxJ+?3W;<-=MQHq zQ)eTLGf5V1EIJ9Nwz*kSoEaXv4}zo4>hrC>)@BZF7mhETeU##+V!oQ}3N1DxekVz! z>Fd4Gc_5P{jUwFMl}^D8cDYg_F8$T#05ifb9|@{poq z_C(~cX-`RH7VBHI**M@q17>;CO-}PAMp8!2evdaB4-a?_AhV7#fF#%70J;iLee(}M zs>*RmMiHJbsdNcovkB91Z4zeTI-ZCHkqEO$V0yk_mo=kAvfXZNpfx%o;SbVyldE-x zswObikpYV~m=gB+;N^tr3UrpAPIrnXa18cvJ59U-QJqA0D>r@D6GGP{EI_|CiW}7MwT(A3!vEzpEg51(Hge zvp9PL6i|Uz1(HhQ4EX>LORNemP=Qqil1fUdfK!2FSgb1OX7pBF?{XS*)0H7Dk9G4c zx}{`H4HDXfMo)~Hm;tvqW&sQxc0nw(#z2)VwEJ&1Dk%JaB-4?~YIw@(d%|fHHUi-a zrs^`?wKWy$KyFoWl{m$UNA{~Dsi=s1SX%MKVC|$+ETf&jW!`xjJ>+Gv9-e(p>&&CI zjgeV}byl`nMazjmn9OWgFelom)E0y^>%5S$&@@d#zJ=vo=+XPu_LH;?vQ-!jGUcye{9`I>bV>z?nrBG)zf53m)p#XBmZ-j5Rm z#^+nB_@eKe*s7tDY<}=v&wkJp5V%$Xn74PA=d*6{nX+vc@mKctY&E^u&Y7MqqoeI_ zYoiWdkcq{Ujp}+-8=zenwR-ieUcJnsCEo4PRb!8-NJ62oox@}4(m788?1EhtpwLI~ zTS&biVp`KJ5pmsYjE0Nd!k`kokdTVU^XyW+NyYmXiacGxzj zb2zNi=f!#dUjCf zIWPK`EHX(kxl3iJ14xaCO8jpGgnQnzs5D4|gKZ9pEbAh?=Vg}vAj{zxa80Hv;465D z3Cx{X*gMM;sCC>1+BLutPQCAoHMorfHJheua2JQ2`Piri_YRF=vyhI-@qX4AtYbr= z;U-`%!0Q~0!4%Y>j`t0m6OTa?pI?B<{P{)vO5g%qMEZ>461;?zO}7%@GTvXo>tlz9 z?|*~oAE#da1z!2X;k}B#=MlkDy@tOQ*{*&aDE?!bEFjN0WJ~=Pemjo(b_wc*Qq*~9 z6Q(AEHu^!XH;db;OU*EE(bj z6x<5@30~#Ot&TbipXSQ_tW>$<`R+e2Q7$Pl**M(7HDl1u@fPrT8Lvrn(l6mFxPv^G F{sGdB?jisH literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class new file mode 100644 index 0000000000000000000000000000000000000000..f0a5db8ea2923edd7ae24ea5030bbff0ca43e47a GIT binary patch literal 11146 zcmeHNTXWmS6+SB|2Fcp7<0Nh4I0>S-Eu}hSbrVi zwCfM}o*vr4vUu3ye%Ez;j(@yTXok@>iD~OjtG>Cr&kel2#OTSnNcPcUG;O;rM%Ojh zYt?;w@9utGb6YL$Eiig_D|7;@!`GZc%eQuIzT`M=pa&MXWOP$IVQxF@c0KOXN2eet`GoZL8(zLFl35 z3;D94O=7T;*e0f3GQY40Qm@A>tn1w_SO&uyZt>x+_-|+r!MzhamW#El`2Dmqx6C(4$0m*wjBKW)+ixNUSIW{sGJVY zetgR0tP~rh?0RaH$@oeH+x2Ml=n7{BjP6ynZCGHN?})72Ov~59&@n`SfkB+4Cs8N^ z7IqrZx?;fwzGBhxKry_yzg)!Rfl;lht+%^b{2q6vcexHPh9(of54T!Y-(M6sTk)aT4$BNj$Rm9HxRy4rr2V|Yod-=i!?i9scPz4pjtlCA=C1pmiBSvFu7x( zG9fc|Q8L3fQ>_^yds6o-oBNUPdO1`~4#I+gu5KLYEw0X@Vp2oB6%3)?T0_0HmegBo zvff&Y>aDe1QDU7{O_%$ssG)Skvn(C(qb`P<28BY`L`kuW-suK++BLf_a(>@+;hJf_JzG}wJvrLHP=FMzWF0U;a1`6sLKjgy$##naZ z9wJE>+KJMEzk(8d;DtuudIjn6Bh)RkstlDfwgxgmBDxu$QdWOR=X499BRL@3Vq+qw zjet2S&?cuzIy91*Ku-Jf2GHd+K(R_mp?MckQx5C-^^gKfGd}z29%o58$_0kD*6G@o zVFjumV%GI%V!q%CWHC(q`o4#5$wRa>UPJT(OV*L)*f(qi3{_RcvEGmZE@c zM1@(#U_xS{!Q8<{Mge*Me~>Une;7N6KVO10d=VBHyZXPG99-Ot&<&Y59@S5R&w6h& zv_xBMX0|{^^2$q_X`?j})qdrrUwP@?8G!$CFOB-rwi|i|e~8inqYs8#V3~U}QZyO+ zqcHW3HY6?(MJrz%OENZa`T{%cx=fw0)4t}MxY5TwH>}XFade|dxlp0sFnVJkG*jry z)2~GlSRow;w$iOd>02QK8*M_(6%wT{IBJD(+UU)6JQhcA3R#S1(;u<}HH8jPht4a# zD%1fzY%VI~;$&5_1){ zU&dLuOZdNx|Lb&xUdR1a+)vRpJimdf_xvXA-=eqi?OW*eZTb#SqL$xHP~O4U8@R4a zp__P!e&5C2JV;OBzw!;;e184?uSxwgo~Gy)egy+WAJg~b8`10g^aHey;Hwh8g0=#2 z4wt~Y+lM!wfp<^hU6$Atx($3muSWHR@gEH^uCNr#{UkogAn`PV1fx&ETyJiEJixq| zpAls4Wsun(LguFlnV*eI=6(j57emN25;8xZ1Q|Yr%wj_3vq_NYWRM~H1uZ3lwJga9 z;af=w)imxSA$^cBM~4}xj2_Y>h*nRR(jqDZJ3@P$f%f|hG{S+SCkc;F6I7w0J;9$z zP_+zHmVsJ=#%?62o5=)!4x}EkP|=go|1tylJ&7#Tc!sv|J4MX|=^37Pa6QLG6ySOv a*5p_8f__WCqg^s-kJ_|PHaXM<9q-@fAs0aa literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class new file mode 100644 index 0000000000000000000000000000000000000000..6abfd94eb083b6046066c70fe1ce42306a5bb03d GIT binary patch literal 1367 zcmcgsOK%e~5FV!`FA9Co@+wfP;?PED_rj@(DnZqXN}(VUia0vEZsOK!FSfU8e;E=; zaOX!M#!gDNGzYkFuy<$tdA{*{WB>U1B1lB{9Tk=el?sCy$#Yyf;)bgP8IFw3 z(Ee25T1ultGHdCPklg-HmXss0PatQ>2^(^{jwg>$7EB0biBOj4w=7YRB^t{T1zFZ< zs=1~9s$@DGNgXEvnp}dxc6u=UmUN6u1d>xHSj2VV*#>GilV7jqYV@_M8!F*!HbE z-xZ1<6xo2AKErM#u+-HFlZVU*Z%>?yEKM)i_d~%5v^Kial5204n8rQaCeY}Kp<=Eu z$nb3X#!jo%+HP<5%R&3Nb@g4LoXGgTIu%x6PA~k02qIYw+?`Y#kx&0bzGa4G+;MYd zOS7j7lkh#f69m@EbXV_xXet8FJ7q|J>-zDwue)mSn80#*<3;%h%)J)Yq5yro@3u++ zpAme`04gZl4XC1BL#s9MHorps<5Kf8%zY1-1)Mzwa4ZIof2Nk;4&s-Cy#jY9n0xrW bkE7;EyfFh0aIV0^33L^npj}1JBbfOG{o$%Q literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class new file mode 100644 index 0000000000000000000000000000000000000000..395898d1663aa943eedf71a28156600c56b69750 GIT binary patch literal 1111 zcmcgrO>Yx15FNLnNtQs`((+XfR!&I|-V3L;ssvRlDup7EPzi3%u9LX++KcV2+FynQ z65RPQ`~<|<2{cl;aN)4o%y{u|7b5cn8V8w@TKYo*tKHr?f!Yz`*P%(EeOrBOt+5TbOURFR+6rgXmB=a1S4zdb zG@6#yjFc$c9!;9Vsi6L*6f^*^(2mX8(TrIRR-!U~;X zhp{yU#vdy@%PGqgcaDyw67GL?$ptnJ1C%_wq@D= zXhOZAJSvzZ(13d_xDRatYX@_qY~Lpszc@$KOx`_-w>XxU!q@0Keag@isi1*F3`aDm;$a OC-c*%u!C_ebo~WutWxIy literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class new file mode 100644 index 0000000000000000000000000000000000000000..4f31ff44bf8a89a8ac5785f70b909f5118f6f121 GIT binary patch literal 2890 zcmc&$QEwYX5T0#AE-|KQLrY6a=@JUHU3ynuV=EG^5>er{sCFwM5YqZ?;%s}jYxee{ z~Z7aPr&3>F>{`3T-;u=0k_ zmND8a!3S*Nw-?G_sq;O|I3+)lxv)|zJ~DccV^MDK+H!vvxjh&mcMf8gP&`hEr$Q-h z`2hK$lbAoYT$Ws_Od85LKOoZ%8dNDo*fF}G^)D6KG9IV7C`-O0b1cs@N`@5J5g3W| zwHP8_r_5uLod}WaG07fJWskk=@mTiQ%f8MtjU{)P7fx+%sX(|tqI5&2By{XLEB0mk zpCQKeki|$F>+8NB`&y6HIa&83_(`l8^1LJ1Q>|2$=Q9X#tGV#qRa-twRCqFVW`iaiuj!fsNb{-TS9(;!f?|g!brECY+f4RP^w3}G_0_Pmch@BX7|iSuOIST zDx3OmGg$7)p%S(-L}{(j?1vpV#q|kz^LeGLEby_~lcgle-?$;_Tcjj6-N`W^Bl~Xr{NDGmxEDtF_raO3rb*bDY!n!iWMbnj_@uL};1| zgXCC*u-j*IdT`1c#BCO}#<5p?KhO5wngyMg^1Wl=!(N^3`u-gBJnyxhu1t#C^lf8s zD-aC-{mrvcaCvng=>bl~Sx;GMZZf_&kbe>q`!7aq{MPE~18 K9lnAXF8&9T?2&;0 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class new file mode 100644 index 0000000000000000000000000000000000000000..0f555405308cecc999b9a320607157fd6a7bee72 GIT binary patch literal 1273 zcmcgsTTc`*6h2*%y?`vDc*BQ=@Blu{ybCe$0+D1z14v@@rQ10`q0^3SX9K^?L=%1Y zM;TAMt1OC9W8%YH&ZXz_edqM^m-in5U>)u@p~2vdcQP-9GWn4hh+Nt-_oB+Bwsn#F z;y5ov5Tz|p=QjT4ho~!bd0zy1m~ZGBO?aAAJLm>V;-hF-KS5asY{CSC*%Vj{bC5sp zAE68kT5D1(qrw9QmzG!d7)(5-3{7Y==$?76J7--BrdR`C8i(HM0T!Gptdtf(S;H%5 z`x;AspLg23hseWeg**nxV!CXYj!R*T4ZM&1Tab)z2Ch6;rcjP5(x+tFM~6mIhJ9yC zlHb;3D|jZg@Sg80jsAaTDUcG!0xBVo#R2j~>O3QEs377dBW|+q++>NHY{*TPxLM?d zMbBgBWu$}$0tinkYIi&+(Vs>7vjp2;rWoR(%F4Qsa6ics)ave+SI`hLnCn?7^qz1k$|L0r4mV+&#);}+@Ors7R?zB7@1OQBE!#Hw${LTW3>LO) z6I6+tW}v*HG9Jd!PrZ8v?cNAi@?84S^M!Dt#DMPHkhOPGzjp^FiCQgq&}p`?o&;-~c{%AV=UttHAG2F8rso zPyIl4d`-K45XoNX>+sCikJ?mFKct3sWXK|4+P^*xY1c62?=`hiG%))#Vj`q!FMjSo zp1|tBWJE>FKWaW@fg!L|<${}C0@uo0Cj|2QXwrcqft9iBfl^Wx;09UxPZoq4e{H=sgD<2IQ)G~&56<8tkC5psp` zL>Kt#lqn_x^l++V2iHIK@LlM7K}5B7PkF@jf9WM;=-39(rNJ3(F?SO)_pl}y5Z1)Q zns{+dyirZOK~21*CYx?3nRadN9cqHMYY-xK-o=E|X0h;|Exb1X?#fPLOn9d&m5H(E zat`FPVm-s7EQ06Wpnh@1XKOiQ_$u-?+m@8Ah%Jib`AcHy=Ld;RpG@1F_pXsH9SlaP zcqoPFMe{&UOYKY1X9`>7((y@4XR~>16c?@M5MDON2nt0aSrFe5!lHP%NGuy7Uuh}6 zQyi&tf!D)q!PSlq&I8sR(BkZnq$_r{`ep(!D>B(q$qwKXM#F^wW=F=>q#@6kxU?jF zs<`cxQZWlIB`CnnWw;J65LhqQ$2(iBT(LXfN}UUK*y}WzYFNtz*6K2#(MjSdd6+kC zt_gf!e>U56g}~iOw@509>mQ$y&uXJl*+s{CXHCBLDHb{LNCgvu}v8-GK_ z*-YgTGiqgPP9EE3gk$DKb+&0NWTpC$6>xF=XG&mL$BCvr4dPJ2Obc( zks1!{ya@|79#pxLwv;enr2u6@su@mYS(zcz&NNQ%(gC$6K-}oE5lt%j1qP@3Zh>?b zZoo6ILiQry#-PVkh8-|wiKK1>ZcU0{VG8grAv^yf{@DQg-!L;y!m;eBfIY@zJ%Rg| ze7y{g#01LK#?EYAcI$T1latqwRn}_)=}dENm@r>*rl<IU%lK>c zU%}sdcsV3qpx}LYbtL%Oe8JaI17`p>Fw#x@pU2f3iS36Yl{XXHH*<+20##aFyeUT+Cw7Uh{bAot#5JU-6wLw^~=_E%X3^g1Yk$Xl2|7 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class new file mode 100644 index 0000000000000000000000000000000000000000..cbf4159dd040aad2322eee6d7e194e7e7300068b GIT binary patch literal 4443 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class new file mode 100644 index 0000000000000000000000000000000000000000..1172440c4a496eba3d271a39fc649a46a98d758a GIT binary patch literal 3074 zcmdT`-)|f>5FUqX@3JJMga&BZf)gmcS(1Vjl>i3?K*dEckRRW zMh$-!FGwK4JO2!CNIZZTdvDuY-G)m=q3Xl!+I!>AGxL3(@vndV_%i_9f=`>UK;Rpr z<0xZ7Mi1CAi(-{UhK-{*Re2Jb5ukizZ|c&XhvB+%GJUz+d=ft4rNx3pHe1s6!;kNZ+{qtauZ zPz-4-Qf6@*I@W5C^2~fjgT;dpr}kvbsRIa|b7A2ejhU3n(gCL>NJx8@3PXiVghv0< z6a4FlYc6Bl;Yh0t=ilb|UqZt;Wya7Wk#h5Y?vila*f}spO4yLoO^iH5GSML)QshKrW2J48dyAT> zN&2i|Zbf<)!$+ZKu;6@_%?j|u|5IT{f@`MZ(Fs*YJ|SK+^JHI$B}Op|W4W}!V&#O? z>_)Ak%nPz`&#kpOkFC=5<^YE*wv%0VS;}R?^z=E~uHf&qcby2gKi}yTLNyNF(U}Qj zgd7&i_eM|W+8rfjp4PE29l+gVuC++&Se-U2<2t}&hzB1RVM&>jn@XH>Y|BK=F?;SY z?OW|QnN{j^vd$Jv0L!qt1OdE8V6EMMQXGptx~_Px^o8Vi@@&BM0mFJoVAW^!kYUxv z`Q&56j)Wn=`u}l@=n}Xxc6iZ82@5_q7SsZIF-;+7W3<2-krRg&;b zu8>bU9qtIz?TJSD`-(G~&KEoqSAlO&Aj`u?UvvswPq6&;>dFuB>M!2z zB0k+Jhp>jvB|i?}b$A134R{kS;mUO!FTiEEg6{xFSMmK8{$`Qiu0dX}Mc$Y~-aG^H hJ2l7~waC}Jx9?57z7B1i1AKzN03+(a2k;R@e*^y<)NlX* literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class new file mode 100644 index 0000000000000000000000000000000000000000..862404558e2dd78ece8ee7b7cce46ce5b9d454e8 GIT binary patch literal 3066 zcmcguU2hvj6ulFi+HoDyh6LJ_!Xz!=@UaV3q)IqMRA~{eOe-~Mq&^@t-ksRf?#`H* z*{JD%;ddZ`1n>MA-grdZ+4Uw~yotBv~@;8q>x2z+aF z;Kxiz|0z3SexPFCu#q1`DhYiPKJ(4ikV)o;%(7U8Jo1(M^jXM8mTUh$3!WJzdn#bj zU1s^di1{AZf<@v5*LA27xRfGCOb+~q{ii&zIAv0k1Fnrw@^SYr0dk+f;;xd$GHDN3 zl<;}@;)mAtFPComt(9)TB{EMQOU;dnj(A8h zp^=D~MK^S$RX^gf`J6Tv_J^F>;}NG0z;nTQS#UI9QYuUPoaP`N?OG}f6*3eWBxdO^gDp@$;k=Ku5(a&+th7@;6+!085N?xBiO6iTI+@PW2hPvEiW`iNMND+TN!h|Pbv0gk^FJ4OAOFPLp5%EZAn{jWkVoFG# zHcYIDsu(`^QUx>3r`0R~kK;cRb||=JIv5^Pb(j*8*39`upOs30r7Vo)(l!?gyQ9Px zwTiN|9JAouTC0=5DoyY7@skC1>~ohzT!u`a?y>Cx`c8Y-72w|{H?x8@&XJQf7RCrU z$aX-w;N^mRc*M09VVQ1|xjax(CQ+GalU8|#`?wvk0U$IsmKiJ+a-LzEQ#Qk_TCf!} zf0dl}tah4ASL`HN)skt#A}qI{32zWsZTC)_Wagven&?tbNd6#+`&{oc?281JQ&tZc zc5fVyCu?>n41w=@|LH-|A+R#w_C)?}dJY5_)YbN8uar$(valxxlG#L~-PT!l+r2`u zbb2|3ogIX@_9&6A19jz*Fap!Qm?o!{QLSUL@$1pcUS-sG!i$Xl%l zOZk|ATp(eFZO+j7W&}1+vaF)=6(s`RK3kZnx}4Oe0XGT!UV++boK+OyRjBk!2Hv_nJ0bD8B-{qbmy#gr8}pxGw$pBoUFr#*m82fLQAj1 z#{?RArPRU03ts`Y4xbioiy5y3THj>Hl|wVI4tNd38!Uk}n1>7auH$G98aP|SmUj_n zS~#d>c=Z*wJ8anv6`8^-{j;jf7qZNf+J H3HW~jJ!#Kn literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class new file mode 100644 index 0000000000000000000000000000000000000000..e2405d38d41c730ed356191a60b70728702fd617 GIT binary patch literal 3026 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class new file mode 100644 index 0000000000000000000000000000000000000000..e5c677ac2ab01110918f208ea55acba4154f851f GIT binary patch literal 3066 zcmcguU2hvj6ulFi+A#?!Apx53F-a*neC$FM2?rK2Zlm~lvX78N2_nbTT&ToJJ_%i_9fU9+w zA#mU5z>k@b{zG=e{6NLNVM9NNR1*3oeB_(00h7!RnPssGdE_hi>a&myE!X}{7CbUa z-cyo?*O}$}BIbKs3l@oQxUNHuz{M0fVzTew?mgsz#VM1T9B^%fl6O1T36Prv7Iu|1 zmPvcSqJ+=>zV_{}e_Z|Udjc1>g%oy&zzeO-0|K>O^rQ|i5m-FMzM-|!4Y){V$z7?r zQPB|(DF!qY5wo}r9ctB!cx*nS&AI&nr}lWrsRQtwb6(~g4VaY5(jKQdh(|k?3PXhq zg+~9QWBhc;HJ1VIaHv&`@l;!fLf%8KOSPh>jOEfc z=L)BzWG`wJWobHQ&bhT#CxKO(UhCm23+!mmT^4Z}GJSf+whQPxtzDOZPfTuR32WR& zPS#i$BV<220qKILbMp2P*II;Sx{b$jOG%kTWulE+9y<78C}rU?tMG!IR9g}_Ryds-y3J=(5_E_Q|Fx01NW^*+P8 zNMNa}0u~)Gtll^tP1fu{7y=Kv&$Uss2`u-yJ(0hY_JIJMy42e2ma>UU=JrHiGMi|$ z+d9i`yIV+>b~mT6y@L>IcM|C;P)8mKBQWf1uKK671EXFZqb^t8?b{tCOS=J-z+V;m zO}=`Hyw#Ghl#eOM1rny%<_w*0MqmRa%Q7loQ6k`{XA3itm*d(r;2i>gR-m>TXB7o_ z7Ha>)gG->frxG3T&+!65;PUyMChfrl%)dRGdC+I~m?zGVJ2@*I>H1XGfqspuaK2>__%Oe%y=a*|7F&$9GZc3z-u7hUyccn19%r_2Oz_$K2^PLuTKoZC{w1|r#(%fUA*|s4d}al&!fQCH!Rv4dSFYmw z46MN$_-x|aoA`VSf0M{>mmsf}BCn4jZ=4`IREIu^e7OX9y%hNhuAhN-M_#`NEgS=U Pg1;t4v|eR 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 yqz`ujG%)~rvj*s94bUVkx3^ zL!wgqogal5@9td=a_A0$0I46=>zDC7GxN;Ye)IPAzW}fTcRZ*O*fcs0GbUvCg7sM# zt1L7u593tzlF%f(p}E^-l7$JgEK>L z#)z<^Rfgl2JzPs@5U0!-x+79(B25%G)cKw<8+WP25b?bn9UfJSqz8`lpa|S2gOo85S+2A#V=vU0kRKA;3kq6F zA`esaS@O<_j#8bJPqO4&NMe#!)iK68Y87QgBKys%lqN`R^tsj|8RzP#vUMe8FHI*X zTQf>N(*mn`g5WypVpx0=#HMp$bz@~O=6O-mhf`tHi!bWzbjD2|xJ}NIiGiEmSI0$L z?Q;3i826bLj)cjnGL-b80mo-y22K!ISc(o#+byew?6llzJQoSc*L&GE*KMbsz!<;an0iGuCXu(oGFBVT;{9?Ay)1P0)g2-gfVy+xk&?80AGptN(F53Sb(G0dN{gUi~;^utVS? zwgV_O!#xJ5AS510yju6G_xa z%1eIPfDPEb?{DmX3_D7Wtu_|yVqLZml6cg^yN5^e?)b-Fzx)OO4fwhOB?1RZdQM1r zmFrA4^IS>V^R z{tA={+(=ahH0n9+!(--Y0_C0(5rKJk7r&b*OGbSH*1G!`J31&Zxw+xKI1rJw#;M0* z%|)ataFxK-W_#2_;7%urG!I!b8gRuA16GeBp=k;ya5JxPz@+3pGb-Iy5(E^{D7^ck zhVQ7PA;UR7r>g3N*BU1g){B1x|H;624xw$_SWOZ_I{z= zF}*z+7oswD-C2Ax(-a<2)xSi0HCTo>7vUD%#>n`9M_fN7u(F;DTG`Y#Hx4k=xIALn z&fPc1g}|~aJQ^GfkI%5PtovLM_}=|@;yXsaxt}E0Xnr+|-A|1qH*=&jS0m{KSS4iX z6-L7>0#DfvCW2+8rT|}MaGg0C5~!|cNzg3n-JILHNgQJiP*16h@Qf?t>H!Usbfp6k z9*PrZmqkosr1Liy-~&?ra|Yk;_gR`uER(8ila%ekvtV0IZE4-@d zd6>(NqzF;|F~NVnWqSct$~xi!Q?=^lS@9VfHUwhoozNb$)-cD~Si2x0tX*uZUF@uO zu}8Iwoz^b)uy$Fqe8H4u(k><%uWxB|5j%}>!g;lLfNWDB*{S1R+DPUT9>zlI%=cVO z*pL@tiRQuo9C&J{RZCXZT&!R&%;{zD#?d_?dI6i}g#Q+qok+VgJP&o*Q`=ahYMFRi z$cfy|Mw;-$$`yQ>)+r!UL;rKg?9B57_pvS=*7{Q@6h@i6y6IEFa=j}O>9HM*G#D`p zE*|MA#o*fIy@={Wngr$M$UewL+F^ovh!OU-Ti^!#x6+I}Tv05KSfn3u47}3opH;xY z8-U?mfzOcKxL~fp7ehHqKNa{AuPZqY1Qz$WQW)G#r-D@=a0TyVn95-e=CO4J+Z9-V zGE`A2qf~=MY}KFy*YLZ9&vkq%1b^MY#%uW8$6+P>)_#TMXUjK#hBtmksRVD~dmeEB z@4y{=FQ)Ap+=aJMBG`NHf+2%9hK!MGB@~PilG;5tWf*6stO=%$Cz`qiYe>k<#29!F zzvtS<#NVHw4KH|zeV+IntYcpZHsHhb`_V}6$0&b-55QOWETJtMJ@+YmZd~*?lCQtm literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class new file mode 100644 index 0000000000000000000000000000000000000000..6793dcef2120067e5d7a2068c75c303d18a49c0c GIT binary patch literal 6761 zcmeHM-E-VD5MQN9opV#trVStcprW)ipScZEJj1Y)Z75W_( z3;zk*W_~C;e#*Ljn8;q_r_q+5&TTTm{D^7Rkr7XPX&!wRv99LIU%QQOc=2Z3;cKl{ z%JsTdv1l)`go}u&B`LPKLQ)4t37pELBuupZwT&k{)C7(tvQ1zrkgDycNo($jAINr_ zt9b(FANGVszAIuoPUDS)FA50;5Z4wF9RK zOb)~?D<#!9oFONEeI!t>OtyJM(RtlC$<(7=B~jl_dXu`1<|e25X_r&Oz%#~sx$!h) z#vr=EX~D#!E1JeBjYSkI^iT2>Kef2xB18!-B|G^3OAo(A)C&`qrnD6&JUvV)5l6vB zfi4TTSew(!Xt{^5L4^=D9>T`UVdFi6jhDg3>%->=?UQ&hBvai=Luc|oWHG&EkL_>2v4+u#&>VNY3M#kp@-=ZAmJ9zJGE7al zSN|wQ6(UhUzYrLXLpvHf=)IKSuv$nv{hs5;1BK%@Q!&Fz!(stTq%UYGhWg`0r?9Nf z*}oZ~Ld?YYUHALU6awZc9^;nXUCuJZGn)3#VXu0#3r~1g2-o$AT>L&YL=Z zGKdAww}GY!nC!?AvgC0;fck@@dNane#a3`==OR3EOlq0?6)~`i8$W@oRaOLhQYg-B ztjBG^bWfo@H*DrC1cgiIzc%xOKJ4)MY{_v2%y9V1-b2^}rWhidtI}y~9Ns0zZdsg# zUM$LM^H@3f3&Sh6*fN=&hF0n3RcV8H!fm`NZ6d4EhQMiaz~7S!F3t=>cD5n~VAvPP zp5OOuk9M$H#g|>E7T&3e`&zP6{nYPm4dqxOtxZu@&=`J(a0xj5 zT86-xnc32UFPS0$Gx)XrqeF1NJ92L76v zfY+P&JCRetTgbo;LIQtfyKdRvTsDkqte2nfK mflvBc&t<&LihYXXpWy{?7q2N8htJ_ln1`?67A(RNEdK}QwV0Lw literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class new file mode 100644 index 0000000000000000000000000000000000000000..7d0e523b4c480004f0d03a04dc8b4e037288c63f GIT binary patch literal 6091 zcmeHLTT|RN6h7KQ%(_q#lJt^Gqtc{-CbgS}^b#m3<&w~GAQNbMdFcpSEC#Q%m9-4~ zmHv^=v@`8{e^jSO_L^P5V5KDt4}I{iBp;p2r*pIZ`un#(0AL=TI8Y-HrYiKCEEfJI z+h%?!n|{h#ewfI11j%dI0`ljv{<;s8k~-z zp~HB&!+3iR<7E!xbsT1tMv|x0*xqD1+@KmE;yW!gxR)(@rDwd-%Yb`n zAvq>-vn7?zv2W`*DFOW~5$<@IPvl;7*gy8E+CiksB1^J7Ogz400-=;AhG@w;)snKz z!QHw!s0xx?Oa)Q7B)7g|5=6z`nYC0a7V1?DxxMUDu_w=26+}f!t03#7l7CI*)|aFZ z?WD|8d$C@|ysF}eR|qN~h>pxTy-Y1_wA0WE)u$!_a8+T29jMi|y7fScGA~Bd+s!qu z7No+IG`|z_mZ>upYUQP>g2+ZIOWcnk!_0uC@<%Dk9Kx;JC}DT1uf*@HT*XemV=&t1 z>7B;p=(srPK+LN3;gsI1lobr7pJ;NuAtT!0S(+Ogr*b{-F>G2f+H9wq$|8SAMX^X? zx59zz2M6*F?yRrG4Z(CpM%if;?vMq- zJwm=ei>FX1S>dz$&!KDjnP_zgxGS=)LcW0aRRqRu8rAILhrpen^qYPv=;W|&#xwna z6J)mpIJ&$jLcA)BQ;nVNlNfLRRJgJ0z~}g?l%oR=2@DiyJ1~WP&VdHT0lXS?I-jEs ze1#W=xfTw5jnv&c!Yl_kFt_j0vmjl-)q@wFcq{odPE)+TG!c{n1-ygAOWGP-fqpoL zGY4np-^Kqc@H!0Qe?LA$@CL39<7fhR5d3$4hx0#-jQk3h{=``g-ojtA6YzN(e+P3a zcnA5IwX1Lq`rtZ_Yw&I+$HmbNoKt+>!^a^=W)=!DH5}(+MoYw8Diw1RZtaQr;AO?! zE)jFLRLnShh&bfd+&LtsFrbUm-4f0ZN;!KyoP7(;bNSiVwhJd`4gZK7%h{6262-FaxtN{}0Wzr?3D3 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class new file mode 100644 index 0000000000000000000000000000000000000000..318f970de11673cc000b9865a4c3d6b5825d6dab GIT binary patch literal 2331 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class new file mode 100644 index 0000000000000000000000000000000000000000..58691163b096b2d83f12011bde735d94130b72c5 GIT binary patch literal 6771 zcmeHLTT>iG6h5;+7FGfj5HApQf~eqSMk8JlVib`ilnYowu+*n!r`a8to!)f!QsAHR z(JHOdcYl=S*E73e2m{l|#2YdqO~0~S7IINqouTW zHtoH+#x>+LD^tHiW61;&i|hWz&K|F6hx*zwbZESKJhG`}7}pman*OE@#3o;j4?-30 zM0_cZg=RVwv2v(VU2jJbLpMans+(MfEDC?*!8&W2u7Lv^7-MhQ7?OA64^RJYyAP?sdP#teJz z4tF1aqlL>{qkXT%jIsq2K6@f23eGpWLX-5;Fip`k+~rOfhx)EV*XFX0k^27pHdeeE z#(cfq+~IP|c)4*AQDf0IgQKI}>Fn2y5Ye#P6E80cVz^`(ZoUG)AEX(FZk+Fc@W1qf ztu#VZCqibQ4D7F&L_{H%oQUu;n6N`?nwwumhZbo@a%d_CM8q-D5HN1#tYb!( zPF6eJGrv7ZA2_3x^QlZ?d8@&baCJ@acqWZ{spUCt=eeue4V4?lK`4=Fqa(!qE|)y6 zAzSQ9(M0=E8)Jd%)gq>pyBkJaRfhh#SptrUjRGxJ+h=v|&LE38Aq@NZHg_W3D%YVC|waq;jJoJvk)`q2v$Qx&<3sH}F5%T|%&l*d}G&YOgJF((y zkZuCKr6^jGb+wG0u9oeZ7P3gu&WC4gLQW*6j_A7hdsFa3B;GhzpA?D@b`cWW>{m_{OHml8gx(g^kyRSqZZ5IbxPReypHuO({ zi_WR6fDAjz?A5bcr>%zMEJ(J|1?sKkriScN*(^daO1dp9P$oHi7Ra1$T4iW8;K}j) z0k*frYT2!homBB`JtM(Rq28>V&7$B4x6cDR$}Q5ORaMCPs0F4?705z%PfZ2xc=9@? zNH{t576|Go1!gi{2q>_ID6;bGA0K) zT&7gbz6Xc!$?EACLH9)Ul$)b>cs0gVTapd9D$^Z@wt8_@_~a!Du5wP@g?@J8E;P-5 zP-rbw3ipAg>fwrGaGfjtKqyTvffL-=_ECuj=n7h8v_{by#Q$?>4=W6m=;~R6S1~wp z*5C*Y17eiMAY_0m9pG`iPvBX=#E$9zl_tL%fBt8B@prUJbREA&8KNKPW&9cx^YttA zD*hd)ZHIWn8u7KBBhJz^tTR>{ON_;@<0(667;d}fm++pNZ?EhdPY?Rdrw2Vp^BqEr z8@vU(PmuEVrAX}D$7&9)H{q0WEkN(vCZsx>_lN)_gh}$%tyG(b{j;XLk8=w!NL5V&}BE^vLG1{Nt exrOI8o(lAKXpug}+vjLsfpj0;r6s(VXz?#LZ^?}S literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class new file mode 100644 index 0000000000000000000000000000000000000000..2db9f94abd808a86b9a28b7c03db0bc8aa980618 GIT binary patch literal 7834 zcmeHM>vG#v5MHHeWjhUR)25fSwCV*&3bmo61!@xrX>P4^A#qbmxt&NSjuKgNkK{Dr znfT8P%)oyhh8JL_!*?WER%|y?ItjxRCga%W>}oIH?w-r)j~BoEMnse3s??)U#T9y? z#!S1g!M0dIcWMQf)eE}iG>n34Y!=+nb!M}I!8}%T3~m)1`LnY;D&Sqhn+gvb@ z8zA;pUB_1FSa}-X7-!4#j%_zA3%~ohZ}t*cy`&W+RSSR(9gBg zOSdYO;uyG202*F}$`As`E=!r=5FA(H<5C%z1+bKS2~tL`JP)%NoX6ml+o*Hl8xe_I zDIZ3(pzP_P2o5|$SoDVLIkhR1TSnx|)!lHk;v1k6fVw6hSriVC@JxfYT(>!EoE=SjC_&ANwO;kzAv`ueJp~1Lsp$8)? zu%v9-e7;dz<)R`p)h}nnVucA)j>E}bZ{0+=&iZkRXwVkY2X#cz<85eX=mSM@_IArn z?N=62+S~JN;8+5aZ)5^bDxkA|H77_)Had+xh|(rEW|+I4C{Ll&$osG^r$kt%SaXNN z@(32rk$Al#`LFCjmSvz&emMTf6w^IN>=XlOoi&Br8r{bO;lewzlA({4H(sR3naVo% z^Iem*l$ZNbK3HnH1SGG`dYb8Krfrx4TbH$i!dc^j+d7JE4Ip9stO1&#<#oAHXltg$ z-CXvc(_(NamIlu@+O7JE)!o)GdraQjvgU}Dzlj_?BP8=h%(0lkyabW*X&SpM4?Q~P}2 zjN5sC@elTf&W)f0_5JY6WT-k$)#6F`|Ihilp@fwtP0$aHEqOHvPXGCF%NiF|cLG-j zPc-DVlVoW4puEeZWSw_jO$Fb^DbMbzBru{2No$?evT_YhV8QL`l{j+QWqBt{cs6#a zN#KlbI0<~|&Q9|7b|8Y%9KxFSumpvNfJJx#Oi-K-vh?P?^dJ;Zz1Ynw!MwyX=K-CB z5A689w`;lVtPwN{QXQzXDYNHb*-|VFuOG`x7VLP|rf@pdez8xPY4(S>$dt*j%yG@O z%pbWikA^`)8Kx?a|DNLZ24Vt=3tL z5Q%pOF#t_+48o(ZH||Q-V5jvX6`hX|s1^y4LM$R$C@Id6p*e*XlR@8a4&|1ehS1TI zlU==`u8n=a1)$KKQsrJe9sKrwaV*~M>Du5{%l-)yE)LGxI*#Y2>tSP^G106S`udJS z$7gMH?)@Vg#|w%_3K@sd^1cyxcYBU|=!JL;ea*HRmA*m__^mdTzD9w`^QTJRppIIx zQt7*J98{c2KZN5TBUD<&OAnleR6=p~#ZDP2t;vbj3j&qa(MpX?g#9v=Ht-4}N(Ys! zUEN?GDV6YkBlfx_Soc+`W48TO3m~2|T^HSNsS7RIzIpC?b6hrbUAjuJ7jK7D8lt0g z3?mgI`JKb}0QJ%F*j&Fq_XeGy9y*ENJ#;GKm$V){aXO-%iD-j9?JS*(XyA~^@cOrF7B(KstVZUqu216f_pvq&izW)pA5Y9GrjXW zMn~vf{FiRZ7h~_?zjPB^bPGlj<#)}T$0$RE^h$3+fiKQi`hKXiC#LkFuT&bAq1*If z!U7UDXG9x z=o7k!d@4d#FSL|G^<%zwIT-&LC^9RgIeWzG^DZX*l*$C6FK7aan&~W+HC=3BcfX7L zKU2w9kXh4RT%3tCN*ASju>i)6kX0Ap+|9|qQW-%sOAp{=5Zs4d94vP_sPw0jt8^UNllZI*5jg1+!zk1uDwb9}yYj{WDWUw=m!ZLsMjJtTi99$$`<~VR27VL+f{jhZ0-&RujL}s}* zuJrxOu=IDu&}rZQBYAIKU+aD% zTUnmUSUP5lTo{DEvd$DfxmXq7?KL0Dg-XPMR6cyMgE22sSC7;g~n5OihVmnVT?>B5mG30)l!?%CrQV;rek3h4TG1um*gY^kBN5+{G)uZOZ+;oz6+=%dhv z4Xy5R0Aq6dZG>;R;hDf)I0n`1j$*X4K%WG`&u6&A{!gZ)o&fJ9nLzfgE=(*Q>FuJ9D48Gfv;e@$mhu%=S|^nIMhe4CJix=~Hl95`^c(a=K<8_@-k>%h zYYQiDsXIu9lROD%@u{>H`R$i1-6FiE@T?=6!Dyb&p>JSz7pnojt>0+j$K}>9wD?CU zvxMHu5ZXobi>UM_))OKk^ec>|C7P@Iumxd9O_U@P1+jsq~AmY(U zldEVmnK7VDU_NH*(V%Vb9z0@BM4;SOA|No;{0jdY1ZG*hGF^rJX6qzmX&2|Kd%PXc zs4E$PdUkHT)oN@Y!PGhrc(g%aX(i#AC2Fkh6Sz8LB3Z3xZ`|*q20q&kdR*}Xk9|ue z^%-t%GYCXPBQ64kdNg-L5Oh5cMV7xnBn+q%m>XM%3HjhdQ)r4h0cgiMEOvE~6Y`%ElzJ2JU4dEj!1AbKY_b;u+O z9F%YWmg7;S%tP)m^)#ip3#D+X?4Aqghj4~2{p?70{M`B}1V z#J#Z+9$T@Y6W2Iv^fMZaLa$+I3(@vip32V(^yCT_ZyOH9~)%QJ2Xaz_xd{jvW^2;G9EQz~Gxw(l~eJ zu-fZ}AqHjjfXaXeZIwelX83k{OiJz!JJMN{cQ8j$%riO46@aCf$Ne`Z(^6aFh@Ef! z9+h05|mI+K}9Erf? zA&U~`6?lUXTAQvVm32QNI@hIo;B@~U*|hP0451^q~-joa)8 zm_`NOCZ%!#d-a#+0NiJsLsq~J&r@CIzL~8i^s%^rJWF8mXPukk;HhOKnW~XQ1br>P zlAQYNc>m5`fm?+9HAXjjo=@Yqip(wPspnIS6MLd79d;LAE(lyZ_gyKz_Y=6a-dfAX zoA`C9v5|fIS&yA5aT@(lw74BO_>_Yw2cMkoaVDvBIwuBvgz-%$d;>l~Ee3hOfVH%r zIH3XSNU4K~0r=cBoxzB}%r{&qtQ_^Ag6lwF0$l&cy8+T!E|D zd*#%~tJnj$2Cv~d6I)vN^;Gcd(3euenMBHsAyRG@l0x83lrlL)%3GQ{0;N2lASfEwVkZBd3KwE3nS7Ly+Jw%Czg_J16d&sc_@24n# Z0JpIZ@EQK*(G@;K{6B_Ip#gVb;$J}R&?f)@ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class new file mode 100644 index 0000000000000000000000000000000000000000..af5e0da807b0e4c2fccb4225f45288d5ebdc7165 GIT binary patch literal 3857 zcmd5*wAwAzEqM2yUcP=M7);1EWsjy zTZz7q$s@PjJLbM6@MO24!p(z zfs^JnJ!_*^Z0uRP-#6TLtQPX9&O$B&rt3mOCAdjo^*oymt(9JeRkF17LTYYQc*+Bczzs#ntWc5;wd#dD zGC$Bt*&A?b&xV}F3Y-`YCxwIhOiE>GkJGUdhwfV{3>7jE8W9W6@Kc{_E`7An*DAvK zFC+XGP{$9MF|;p2ZvIOv0Y}5J14HJYup>@4FmeYeiw+@W9i*(2Qr3xino!nBDC=aD z-JpTuhQ_f+%=!arF-82<5Cfi6i|ljax^R-jUD-&Q6A=xSwrTDy7Dz$TFA9#V2*6aVg{LN7D*^Vwzz);(9{U zGEhRq9NY6-EP-5ZW>9=SH|Hvf&-d{BN#JhIpRC^`2R8!Z zy1neOGsYBaVn(d*OYCsjLFd`{h*W~FumMk{p#%oKr@J;TwDFuwc%5z~1{=MDT)h6>)_#o5zj4p8|WR(@W4?^n3>XR_)x-j`4Y z@VbNdm1JCjb@%{hi|`?Qg!j8RjxF5BX9Y(eO4^=rk>51Js5RZrvzSl@^G!O;HmffrQY+p6pI*&n)BF zsQCr_2z~^QNFc#GKMHZiUT5vBiFc)@$wMD(&)CQ3_MMyk&%b~C1pxQqjsrCUKgQbk zA||BwnC&yqSCJR9zUPN(5O{I$#EVyYOfoNEhD9pip{MN9V*%?MuDu_WuJ>^FG4~CDbE`rMvqs>h*76R4nRVpsK!d>Ci1xnL zO4s2PQvdZksd=ozeI8I$urESpgp#zc)o#e6_zrD0wtJkKgFdI0fop}isW9p@DV3qS zoMud1x?!k@sgQxts84u+kM_9c(nkqiQvzW4b3o9zRbh0Y|}Bfj;w} zur8-dXt|5fph5_Zi_o|!H0~iZZUT)vgl35bipSL29x>zhs6iL;r+qXy&ld4_E&gue zxT%GtJ`qu0X_NZiU?7w@eVlM$M0h%cPu(OY(kL2_kAkX0k5nmo9NFF7*l6aPZH&*u z)F@#{jiM}x;%1|eR~58b+EtJ}#GKS3C<7-ic~eO_2+NSkQ)PAp|C?n6>S1B1&~P%* zC*Yc|HSa1dm^{Xn%|^A&&BiG@c^bb==ILX+kw@2-ek;5J-D&izqCE{Tl14CIE%KhI zQaev;J66O^PX)BgliJW)rfM(kVg*JS3|ahX8VzDYRhG|6Q!K#P3Ar>_K;l6ko3HJK zDf{V)$+HdM34E4WR}no$cimIAXW4>!VosY(uO`&t{4Bf-uM${jbw=aMmeE3X+jhK} z>j=p=2hlFq+YBcb0`nc^vv7xD$-({b=8Wlyn7~h+6CKIgsE<*p|F8;dVyP#v)an!> zuD#sJf^Asf@wLX5=t^b=8VztOUs&yA4s5UC%U2!^q@4^lz$Znhcwv1D|0i6?Ass3#1!ON=eQWn0+AP z7)K_X0Wn+y{5gdGsQ^bSg2y>LJGfiHy9BSzKcW8peDimh{Tp{Rn8UNB0X#0?c{Zhj zi!h(uy9AeE1{QE#gV*47yf$(5243I9<1IWKg0I+3CZ>k#RLqqUF$<+)7U7*kF;}M* zLrcUgmWo+|>*$Bnnj2$cG6P0_dbfo0%~H`J*B1qa|zh}jeb z7nYy(`RU2uKc8O!FoDY<^bjUiJF3_#ZMkJ~u_EIgxe%L@to$H**swB{oVbdm1d82A ztQr$1AC+EZKZFy6tAj&eUDMlYD=s0Ny~n0@^6Y~koM!O8V-Ri@jV52oR9kO;73As9 z&Jaekg(S&9)JKGV%c1GcXG^ZvlJe?$sxGQ|>!=qmHP>#FL!2Yr9z*<#c(g|t&%K$~ f^>V)xyF)-I@;|yNpr3mW1~5A4i`)lU7cl+>=DAtU literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class new file mode 100644 index 0000000000000000000000000000000000000000..8086e8d432bd0c7755e2b907098402a7818962ce GIT binary patch literal 2151 zcmc&#O>Y!87=FFku*ozK2n5=IgqzZmfHZT-sURc-OB=Keh=f#C)q{7&$(qdAgY8L^ z|HGLp5=e08!msGxKs=t^4M_-EK(sx0#viYr_rs4Lzkd7tB@wmhYKt0-{&Y4?3ZZoJ zNNkHFHAUh?nWVX?vczRuiCgLkEs{)lQJ75Ti3#0A?xeEx%IIXptX65St-bQQca+St z_He01O-3hc++66*O)?I&-R z=30!7T~}K98;oWbI`7d|y;XYi+TuE+OAjizFXXb`R!((ud0$vjNH6V8t&JBUPRA(f ztWbEXmYJ5 zx!)}%4*+pUG47|xQxW{~uH-|Im@j*-99KG17FEc1v9uwr)G0h{SW{s9xWZb-<1`n} z@eP$r_g{L+BpinTN|A1fP08nxc?>!rAkZNO9pXM6;ypUVH67wTI?VITNXJ9=h45*Q zdlX6Tlt_6$FjyA{*2Q({zQ9etE~+Sv^>zJs;=1PjQ~I#}2kdiGbj?=0Vs6?^SF_Gm zsWYB9%0tMH6x6M*JOm*TF;ul z!*v>A_la=-Lue6A)0ruX=o`pDM`^iI6UPt?G35-Ii36> zwX_0v7mLxVscb6mK$jR@{J8GdmzvS(&IgnS$n3ICp~lL2sr|1C;5O~;w&+K^3x_G+ zqRWgRvjdv7Xz{>%(kBd~so#`yNMH?G?C5)9G=>+B(HxD_G5lK?HE4pG=;Nr-pTZ0t z7Q8v=oIqQ`mInUO3!468=F~Gf{R*Q7&Egv%0^C`Ar|N!0=jhvdE~4+gF5KJz?s#Jm z-N6_`VxJ%5d4OKP&XCDP{D&Ebygm=DNgR6`DDn_*9^)U-h_0YbppKX5Ct7$1oPTiH literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class new file mode 100644 index 0000000000000000000000000000000000000000..2c9e96bd904ff41bb6f25c83aece80d6a18b0597 GIT binary patch literal 1616 zcmc&!OHWiW6h0lm%UlNsQB-^uBvCi_Zdi#iF+Ad!5r{kzmYv?h6nfih+nF%GN%tn2 z=-$15!JlC~y~7MXNKhBJ=;O4f-+A;q=kvGs9|2$m<|C*O;Lh?iXHusJtjkhv^3<^+ z<;rxj)MbaMTWB-Q(u{eQn@p(Ggl#I0xG20dI$d8zd4u(oVc7<=PU`(tCY8um1}YJZ z5|}LU71R6adh0-Nk4H{e$<&U(M@DZqmI;s-1SXb@cAja!!&FC%?RF=ZX5PI0Lg2y^ zsil8PfIe)Vply1KCKsDSbBm952+Y6gXfJco&|T?dO9^z1<-!a5Ol#v=aNQAzn`<4V z7~U@u7+t~uBbX#mKY?Gd*4S~FB9*V(+6re>S7a3PR7h2#(83ygpS#C2u5Psj^}RyS z0FZ=elYX?6vtXCD1Ra1RwBf0ARO(DxOo-~?)t<0IbF{E$O^)*G4&G%n;fgs&_b{*S zJgsB`4F?Ab#t+%PpmP{`f(${2kRb^&BOTXtot{!t_$edF?iF!j>`K57nK$SDp z`h+|~=?}5~gKII=;c5+Hm?kiNN}+}7syC$;Yn{9$>{ciq0#i-Hv6LAWG|I<^qrNS% z$VBrTnhe%&ylFa?i&ZQV0uRpaOC{4V$cETUo4 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..22bba1f5f3a77a1d644ced4c3b9c2c3320fb7a35 GIT binary patch literal 4564 zcmeHL&2G~`5S~qcl7`ThLitBp^+J2GCB%s$B$T#Nr4%F@DuK9c>`k(%y=&I%h`tc7 zfCLiUc@f?MG3z94L!2g&Qc$ZN;&`)PW@cyRn;HA(@2}qhU>hFgAxGeStUcH8*tjj$ zX0E3~H)fIR1uF5~*gtfAW>~0v9=OWhy3A*h;o7Yzj|E%I@Tv%TnFU<>OqZ2xbB)_o zl$eKE0?Vn`fXRmYy4K>JAu#`3NMT+Om|5RAATV1->3LWnuylgm)>`QTTqA{FZ>8q3 z3fkPKXk8=%W`vS7(yA8lFn&gh3)Lp4<|yLS3UI74r=yH|OiE>FjnkeGhwd6GVk)FB zG};p!;Z~h%EUaT*V_FvhkN>3>pQB=HK*YR5*5GsvPwrq`&>)P9 zgK=?=Hg*S5lt~-Ij^I-ubTP&-!wzZ`Who_!3q5WQ zg|(R$47oH0)ROE>*qNIht0qu)>7ji^bZCG&Uhl*9f_hwVNBp_m)Jm!y!2_(K12u6V zFcYEolr+r@shA5B$+@WLF*rEfMSW4taN=nukHU^YzGEfq2$2|Rd{NZaCfWn07o&3kEQuXI{NYvwDKCz}PhLuNjYGt*slwt=#I zftWX{Rt>FdJ#t-jw)5!K6ZKdECCl-u>M*0sZ^g0LgxX$D1DXs~A7r#N_-AeHblz!kU z2TORh7{JdmUP~Pm+<+Ba0o=sh9NfY?i(19+L20)$q%CJkyE}%odl}N6WJtr7i`L}e F;UA6i{5Svr literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..871cd68e3367d4aae17492474aab246e51bbd524 GIT binary patch literal 3403 zcmeHKO>Yx15FLlM`5-i(~CR(*49-Ak$Qf~D)wfhODj=*z^dBeqMz@$``wmCf@@#vPN!cZYYp)sClAE!E8 za~a?Y9j#&_ZhqoG5Ln(p?Yr!G!lG2>ly0 zTgGCgoG&o7BAUkNqU@(wanpb!6(+*A;v^CQa+oyX8VgYxtJA*F8jg^kr(Sn?V$1(xqldJO)DLFf|k6pjJ_qReGg-aOD0RHUr0iX~FQA zT>xnAHB&a6;|>JMJ!#4h8$GS0+7vuOqd8_9L+Qu#xG?A$Y9-g?#Em)ktdvi~%}@eI zNY0#qQ4={a$T9W*GqeIFxK@RESR}AgZyxro9jk@xuDiZgYvK{#PUALqLwAMAZML4ulaH+H=<=v$l$nEHqt3 zt(V|7DQwTud@sG~s5zW=j>bJa$DR48Tp0H`lWP*Fx>{_hVI`K}9wDF3r^Iq8PjrtX z?lb{+X>p9^9!sFIqf#C4O?>+$@F*AfSgks{$lfD?0>0Ga9}CE?^RR&9B90#8nBcwg z6-w`xE1#kI9Y+PI;niUPKTCM64pDF&mT?4d17{0x6Q3Mv1@Fh%ZcSjTO=P<>1KZsR PZ1o9jXrdTR0UrDUymVlb literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..aa2b72a5d135b76890874aea9e5c5ae91c0e2a2a GIT binary patch literal 2567 zcmeHJTW`}a6h7{{waq$qW8B7&wWsYRdEtp6q=^lwsAFg=L-6J%Zs#tsgY8rse+&sE zc;`nUj+0$FwNr~UAtCXQ#Ew6I=YH&;zrKD4fL(Y}ff9kYMu&mr#s)psXF;fvz_2t3 zW0ggLiB5uuS(d1X$ANOgfJH2|Tn8VOZ7ILzf$!@UU?(xtP zn0YRwurCNqZEPM9D8EF`3e*V9UtxE(R=NtaWcvL(sku>cpGOpRN=3}9P?DxvwPT)` zXVj~;jybhwDW{IWcf$Na7!8?}%F;Hc7bHGySSkz^G7=i~iO+DU!!?&7O6X{n;QUjD zYZ3Lsm>EMmBIf3AN{KiMb_%2{JYii<*U@qxqk#%xG<=MPe;ExwkA{C9jddC+Zm2Ul zVKzLb7Jb7{Q?zuD4`c1SSo^uhCTB0aAd*yROO@{W=pU|aZ-k?-L31%e-6qUpwoSx0 z3DB9+%P=j+F*;XumFmW0W&arxgF8}mBS~WsqFyFTxyA~R#_BAzO4B06H#S{1XENd) zfpthbxPoJr#c0bF+NXP&v2F!Xlu4Hp1@Qm~LxwpqLJw*cWqFBtwZ{JLX~@&O2#3pS z5P4{|klitKqq@iKZ+UH$Jm4&HE*xqJgE~(PER36o(?LTkIku9Ug!h>i4Evc^yXKD@ z@vhsA#}@R^1goL6x46#o+Zdf%Y>VafXf^&dHA9^VcF_$=VNUZ7wR rmT(5JjH^>{7e{4WS%LdFa`*@M9p-&Ff_HfYZwXd$e+eGL+6D7(sw_yW literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..e3ef8814419cf6504626616b06767b0b61b20c63 GIT binary patch literal 4968 zcmeHL%Wl&^6upzak`QPqlsD3-8`_P%K`bajLTM{iN_1U`X}K-`Iwrcs>6K`nwRi#VRl$+_3}?fL%e(-#2PfQNHXAg~u}&ow+Y?jdV2 z*HfVzv&i)VmH2M#AGtm=EL1)ZTxD-v=CjCf?d~bb{dHz|U4*>K0xo@~t4g-G#_c*X z&cQT+g*I)#X_v&ITZW353h4`t z`UJ>-4<$6T3UU2D!9AZkUclm*HblVVzbVD%DA+0xG4F^SaJr07?x0^#A@qxb zesNCx;25VL8O59A{{igQKVN89b>GEbu2DA5xN;$3K8^yTis8`0 z+0}f~dM{#_7;6Y1P_cEQuF@(|gnOj$?R?UI(y@DQA(M{XcdjuoEq9J&&b5{jDDS95 zdwdg*L0RvFL(;hr^g7$R=W^p}-tH-z|_+9=C#kY&)k5KuFs{+j9 z)nWiY3wW)hD7X%bxB|F=y9Kz3cNVpT--B$oGT0U~+3w_EyPLuGB!dlS0@S7e4}JsN CmyALH literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..3805b44ccc5caf6ac6d5f2c79e21358da608f0a2 GIT binary patch literal 7537 zcmeHMTXWk)6h2DRI!Q|@PD`OT)KVbfB5^6VCZv<*(xf;}X+k>iww5;bCbHJmt}+Sy zFlJx|-g#w)pTqF2)AgBVP3iN%mFCfH@Sw#bcP8RY^EJV$Y)jOc7BYMkNv!Cd zRbadQm4 zw75mDW!7c_rWBTjS42HzI*zcuvv+;6auVE4Oo5KGK385OS}7M-AIIRV$rr;dp+qg< z(_tueA|(a3D=Rz_ED%3&zsi~%+fQ$?AO;KPD-Tsyl2|xbS)PU1=LHXZ!)~Gkv&o{- zI@hUuvjV(0Hl7hfM}*h1lX0sRaiyNHD1?BG7|WGFHfwUzeZWH=8O>pGa0YrW(Hj=M z+EvP26v=3au2?kteF!Wv*y6qoyJ-n%96GdHkp#!hyY|SiA>HjIV)=iU@f%Zq|6T%5a9ETY4;5V-P3a+mnO3 zAt2`UlhM9KB`9JwvCz>T;qTh$&--Uj%rb}QJ&SJUY6_MICT6oXd|vID7}7E|CyOp- z^s~Ihqe%G1imxQo+is%p#2DPF48u4GCWgqd%GJZ^P{w3d8$7WL+nAhV!iAHDi#~Sb z*o)d0$`+w7B1Dj22PU@8BOZFN$@NG!@%}W%nr}Ovi5T{}2)OzmPf2Xz*je#8xHDX~ zn77I5+`a{ebP&kFH3GSVK<;z`xs${YCk^DcY+rI^8wYJN?KNx-`Sa}-q?tvBIO>=z z;Uroqj64;zXtrdeO^k5rLO0@CAO2rLzF<<4QQd(nW2sq+Y+iFw6Z1bH~04dAxZ z4PAK@BwWGY;4k|#T#q>)fqw(K5hDv0!C45sN}YE*X~?ba~fh!PeaV4E_3B#Hu^ro zN0O@{nCe*bB#)UpSj`-iT)`rAdyE%NripJADNOUwS2nCao!VWQnzH43a)UFy6+4e4 z6H8O4LtkaHgQQ?Kd>L^LX=@#0LGeOFoI>u^vDcii)3wRN0WCT;X4-;h(BnaYyQgoM zy2ix#j~3nD+m3z2+D_Hem%ZAjeTCkY=`5mu^|?jg9p1k(?W-fENsES1J0+X&*l5@1 z31f>EjjYHx^7uS@R$wB}olV7Jk*ai(;)yjGp@NF3QP2XsSlJ&JFg;q?S8pV<`^1IW zVRUqZ6;t+G^h(kXZ`TA^yR;aZx*c(vhk8MvRULVa9kj_8L-ef^SK+Eea~7>Vo7O)_ z>WSnfS^&izk(cOUPq#dws6>^X&mXC?faoQm6dLfRcMR(tsv;gQNTul}!2gPVNENx;ie7?PK}`oEnQUR%JLg+7Q50i|z6P@c zbhmBs6NyyHG)R+}{{~|U;~vI+45Aqf<8$~DM)TOSl)Mkp16qVczojbP2Wa+R`u#w% literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..a4e91df0b6540584074c38a97214933e9a499eac GIT binary patch literal 9357 zcmeHM>r>>$5pNO9vKw--xw|}kPOQ7!Igr@9c{s0wkZ`~m>;=vT#$`K>Gb;_d0N?H&|=VFPIx(~BEn(683?&;U`{Qcj5{TmUj zQBb1^gG3ZsEhQt>IuJ*qW%>P9B!ZUZ`mx=L?8BBVl<51mbXz`mTRrIpGHk7cQYg9S zh%IsCbcJ$!?@{PuMxE>e+g<)y^OU1bYBXuk^BTJ=yl!i|b095+A*rWVeAmS{)*Ce2 z&cJq5=y=@)gC^BUAPssUH@D)7D8f{I$LV@P#UbX8PR}p4$IjZq64U32KK8%$I0ip` zx#1l-k<)SIvgi3q3nl`y?Hw69!gc;A?JdA#-E&96jj`}r`^_j59wuhm+pCcDLr1zc zGpXgyiwv4-zw4+T4~Ehm;+|@^ok(HO)19&L{g}6uc5LOa%(Y=%xW*h`Kk?36!dhmwq^Jd$w z-|zT`tv946LpI2TY5K82tEFdGXWwC3dQ-C_&Ge|1ZwiyWY{Q`I(8I*S^N#rjR54SS z!4Xf>&kXw4#oI>EnL4k$c;7D-2s_`4$%U0dOuITRT^|w6FftvV9UYr9$wIAm158?? z;4|oHr;XqW1Dgg-gGdn!x?`j1Y^{$g0mJRI$f+lJu{PEZ%ck`H4a!h8K8* zjJQ9aH+zNTPScw4!k;SG+-^^5lQ!A-nw)qwlTcBh*-djpnNDOnp6!Il#oQD4hJ6`I z&q5&B5B)yApTwB6&8EeQkwVJQ1|h5ByMZ zU~YEd$>UA||IZ-*$E4(k-6jkQ_ojog<9rHGtzNNm+%lZw0X%MInN1l5nk(2F?B#4~ zGNR5;!9Rl@#K=yo;H(75R<|!ACBx%>sZ_^iqQ2jCOXZ|@uM7$ARwg7y_+n?}gXa$Lm3S&$8VHs0{h36!DBZ@ZwU3(G>H(K`WbrJRWB2{sVE&q#=c22t9Kpr2; zP2W1?#IJw`Gt1}e8~wm_EZ9L5qc(%sk;x}X2j=EE^2eSn_iy)C2*Ig=jUa+ zdDchkizTGxD~=_gSuL^zuo}A~RnW`8_!O*EqVRJ0<@fkOz%`B zCHCgX9h<#4-#&ziva(~t-*@0wRIH?S>3kjO@J9MJ5~7&QCbF2sfL(ZVB|SSbq1wsN zaAs-A^!N2;hU-IS8A@M_E$62R<;p=z}IhA+o(KE8bXdRCd?ha?j27OIG#|;t!%-!7ZIK_n@m!4X8P;w8g z9^PqYHayhdwVlYQM!z%29@Bf{h@Y>~?+w};&rV=|WH0DKCiEJ043gv7IQLr2aRYCn z*}gnbBdFpe$I=>gp{IBstC53qOSP^>Se1Lz(&tj6{>aO768~{@n@$wrIfV=OTo>O$ z)LQo=MYjpa@i_h*&7w`AH%m2|#%LY=32LBy72`mGZ~l5-!|>d7x`E#W?xvn+IG+DD zeS^N4;`kO_qe+ath@BGXTPe1e0R0`b@1nhqc?18APwD0NW^aE&-}?+-6ZC!jGG?NG z&>Vhe5-6IdAK=TNAJVIU8q)U~##(4Yj1%Z57=KiN@$*8AKcSxvF!H{6Y#Dv87hwBK zA-12>FH+8bnaZgcYZ2GQSbtT4#_HJa(?Uw);-%5}YkK7zIcBF)oyH~lO-kc$3u*i! zSeH{dUITX4#nT#;oq2Uy@6m&l)|En9+3FI#TrJQfqfnFX(Zh4(nbBAs+Z(hF8i#hn zoU=}Dpw$eh{hR2VHKMl)i2bC1*h$(<(GQKN1a*;gi%=W1McX-xA5uOfHj_nFj5T@$ zW+&+Ffq#0er7{bzwS)0pw0F?<(B4BMdLNCw*$4C?eS|dz{{Mh~fqx7C9_FvXXW6t* PJvsoSOFqUX=)?a2(0f#y literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class new file mode 100644 index 0000000000000000000000000000000000000000..4ae271e432f2bea619a7d6cc49279b53d9997639 GIT binary patch literal 5951 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 u|5>0b6My34WvhFdr2IyI?<*71bu+W!E&xAZyy literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..0f9c2bea21c6b12b014a30653ece01dad3eb5971 GIT binary patch literal 6232 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class new file mode 100644 index 0000000000000000000000000000000000000000..92c51eef81a0d61554cf15b426affae18a3727b9 GIT binary patch literal 7421 zcmds6TXWk)6h2DR`a+;>(km^XUVsEzx#d#YCZwdf7bj(!kPa`*DALAOB5Pe~15PDJxmFVT=e%_wwgf<>aX zZFlUN!&|k;Zr2>2$8IfhcWSOJ>=t*KU*qy;t;zg03u_B;;0TWg%dIvK#hMp1d^X1- z7J9bty|U%hI=J4HkL92pixLeRbSBxvw}VFQ>EF{=@3xsiXX`zx zS>KMLNrNhDUL&wUi1~|e&QE>5zCJk(sw>apKzJ>-9CW(@jGP(XZkD}_pdw8)_J zrSXaS0u&C!QUYfeO_1N%XKLy*sbyEw;aQlZL>GWx53KnxT%HF|FYFZrs-?p4W0PVk z_0h863NwSw!PGtLWe#&LH~6%}!q9WsWSKrSXsOt&-315R)u8)%BdRFNZ<_??WNsv9 z=UU&=GytLD;1yT%!*RO}Pm7-0VSoUU!F{qLgI~If&s&>(r?$uf7TSnTOx}Dmrhl+Q zyTt?xbHQDvnlUGDJlmAGTQU(@D$obuWtq#u^di#>TrWhj@OKd+TP$RO zgRt5Pc?<6^V$8Xw<;X;3Zh1b7j&m26VPh#k+je&B1~acC+%05q2!sr7A%k042Dg$7 zZmA4@-E=vNOd0zvTR2Tq0Ge)A?6E_TTJm_X62P>XD-68kaF7|uYIvdRd2aFp9IP8uDST7>@r z_v0MpQISH4oZ6I#aUF(7N2YSKHRCwQ-gVYtg?`#y;rlaTBbwzwB*NGcJS-ACT{!a zt&W=U33;l-fy=f~FySNtAoy*^K3MmtEpsKtW{S$R1DTu^;~Y`7q7LQ)nY4(qgVlS* zTn$B6m}qi$jmM!g$TE3Mw2Do=lC;t_MOGJV@dEAV6u3)5v~fid9xf8No*dPpYGvOF zA`gMAJW#d(F|+SN0FOw@33&v|y4&pD2MTUiR(89Za_qLe9o@Khn{Szqrd6d9NdY9& zmKQ}Rwo>K;E*{lNm%)RCK>;{j;=6ZrsP(K5A7yr{VB>v>jxrD)bM7+)T>qJtH>g%j z)j3c<0zR|qmgWVhKa~~juo1U#fke@X{LyoS>1t(zd${%;bgxV5;9MFxl*7T1=5fsy zyW zoV>%x>Z(B68J(jt&$HWW^od#xF9({1( zt03*9tuD5f74JL9uUe#e9+tYYU z&_k!`a{p-B*ecUk2EEp3FKH&BV^7EEfC7j4K|SxiO~|b4eR6yP?YuHQFzDLBileQD zhBnyu%$x%popW_VSU_vcpj*0?tqcu=#;4XF=6Z*zbkqGqXr|Wt(-e$auB+h2&ynQ- zZN~Miaff ziu=qGj|7>7e=6|5&IuZ#lXMEt65fWWOci{e!fZ8pe}_i0xp%X<_ma8ObOv-?dO0T7 zouyH5Rouq1S;_4jJ`0#L@LhdP=YJZ#_$yuh18*nj13cwwq7HqC=ZK=Bk02@0wLK%( z@rFM>=q4oV?*1{x`uTq{fd4O={IAeB4X1icWc%F0KDY51lWIt%43dhgXIFq>j3@=I z0RkAB0^Fgy8IWHj0#u=}+*1nC-Kz)Ymjm|lHTU|O?qz#@lkFun8@$*3f3VlX%y3el xx3R}5nuO#-G?kj~+f1Wrj6cHXJAA6Jnn5!(i(gNY_hFh#9CV4k$NLb?{0+Z`JGKA- literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..e3d5b0635bf2fb7d7bd68f7c6b0d45c3b5dc17e6 GIT binary patch literal 7241 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class new file mode 100644 index 0000000000000000000000000000000000000000..1e38fa44469d0855b320fcd12da82baca651198b GIT binary patch literal 7887 zcmeHM>vPmp6hF5Vvb(%X5e3u;Rki|Ib$qfFEu}Bqw%8ZqIL@TW?KW*T2{$*LmT&%5 z&ghJO_m6V?-DI~-XlPQWtuW(<-6S_Bzw)euzYJn-t4wqbnj83*@%%I7ry5O`uQ$~U_x~7GXCv;X^KI(bCG?mD}ue2t)1S=4pS?y{v3+{{t zQgB@cj85}dvoY5U@$khINxTGKL!Xwq6Y7paxGfPvGV_vuY{F+(KZmlT*&nzCmnz^4jPZkBFLC7 zl8$?*gj+_O(@7QBo8OtpQxY76J~jf94oOphd@#!O2oJ>b$CCj$b6b>vvZC4P(@@co??$CNIp&-V?4nD%#-~`Vc81OZuWDtMO-%>4u}8 zJ!OV#>Lp+Dxunx6Nj&S95$*|I=QF;wp_-8#U|ssU8Fvu1D*8j$v5>7{gtifHxm~rB zo}xai+K0%q`KsfomH`IAxf!{D-laD5nF!1hD&6$dTQ~tu5!I?m%T^%M;Ss4$DaBvU zwz}yiSeeSmG;j4pj5~UA98UVCP#I5k8677K(dZ?OSVB^(PDvR&j61-%UOyH-j4B

m8ds|x zmNKP8Ek$%#5|xVicmz`5h1tk;Rn8WX`JM;LBC2v=r7TsVeR#b{YIKR-JVO`gEk>8? zsgY1(j;5N=oNuAcnT*(3#; z6&hwVSwe;~DZ}U--{Mx3l0nv6sV)w(9LKB>qfXPdcTCRcR!)Sb9TyK~5wjI)Fq$Z~ zMwy(4KiXXG2!ROK>cib(@7?6A^oaL*O4x-c2r!ZipP0f!*Bru~CpS&J8&!HGJFmth z_qaeP*zoTcjMn$pD4v8KQY}Z;L+f6iC4=TcXsh5G@f5sUAP_4cOuD|f{xiCip^F!x z-1EJw$2W1VE%=eJ_yp#-jBfVSO3FvNu*7>Fr6XgDt-MC%s63R+N&7I1>shek`8?<=vTHF}cy zvX$gCoH3JZgF}emC&b7)aGZ555oQ3H;3*csCdRefl5;e+vj1c(t$>zMcag z=D;)hkZz~o^>Xle(m&6EC%Qv-VS}E4%7uykMvj2L5p z=Q*f9=c0Z|U%^?uKq-%A=AzxB=8lXb{{q#{K|MuZL$5x%-_`va`WDwjqxcPAozVk& jh>#n{aS}X#!1^GLQ}l>hG)s?Zo)#h1GCjqeK6>yE)N$mn literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..92b0f64389163e42934fa6f0158a247568e9ec79 GIT binary patch literal 6919 zcmeHM?Ni)D7=MDa$DMtJR!6BC+Nz*6QR{1|Rmw}DJ)k3$j(%H`2!P^dED^ z&ggglD97J!?jQ#TNgZU`89(Hb-TdQOtY2@dE`s;=r?)P=E|Q-<3I};FSOcH=@k(-B0d{yp?6n7 z?56p_TsBjoL5D6``Vos8{_^@J4>YE@{a{)~5yl|q&~SYpwxShCOgeOaTE>ZHv0h_Q z%7>`x(B;ko`EN&mPC>857vgP^i1mn1#j(`P5KbH#sz2aba|L>5whbMJ2K8KCc@bS1pU7=yPr%pmlWDuHc#=G2Du(!tFox@qY^}>J{{fG= zVw#&Z2d7~a6}slo#eJ2{Dkaqrz3ewk~46l(yEn zT4mtG>`n$OT4N9~#+}JQ-4rkyekz)Fr~+-RT4NmP(NNbMpC6t*JtI8JpQS8q&E-x<71{^qVT~U#~ig^@A%dZtdaOjX%rb{Ob*fK4!t-T zQz~I?B_Oj_7E^IJ&yuEbf#YoxaR&x&KN_Oj4vihZ7kCA}Xm&_D^v3wag8f|^qVJqb zw~znYI@M~ETiu*?TW+|aVQi*aI_02+~A7G0o-;&$ri@XQoIXY zFEF9e-4GE^YSq(QCFEGxFwkbf7He?#I>L#EGYSmhjPh_sd3$G+X9J;^ol)1_Q1Zkz z(WS+7&~!CK!*|-)Xr3Hmxn~YQ&kC$G@k|E79)Bjnc@3eP>3Sb2|Brknq$E|tg9US? zH;a_?5>RV2G1o;TEiTm}I#J+`w;wgj5PliF6#|?NPILH-?y>2YLvE%xq05jg1$+fO zPfFyzMH{kwz9vWn*D~9ARbfIFai68L-c%4E(XI1sUg(wGFQRBlH4s#UXkq=2WB1DPBqx4wIPN$d5L zwBs2Yx7_SLk>yQ^5uV91S|UkMJY|Vk2B}r&wQkSYy4Ys9kClW_wLpq;IKJk!aGyi0 zd=`sZ{c15-Y@A3faP4Er)N+=@nn~tLw6>_#A3{hi-ITw||S*mdg&Q`d$b=y^hpK{ZKxIep3}~ zylBN_DrkS!51n*CT&W*EtT27Po;#8-zPb>byg(${gfW4ZrwW?8=(;Y%=zm%t;7NsM z9eVPgG=jlVPqI;=dF*aa&J}vt({!~ARjA(6W6!h-o)@!J#B>);7kmi`$h=H6+NEeL z;g5hp8m2RJ7NZJA12ly3dAfk7i*|g8YPg&CZ`$!?``n!yv2#}s&7tK_Z_(SJ(nY=l zQY~- zpU|hEYIv-H*IT$7K0m`%aj>X|hc43tc+Qyi`!GG*hx{OYu}9u)tpw>WOOkdV$FDom z&2!#mc3;BfDUQ)Mh5Guo4}a+NLNo{UHAdebLCsiQCdjhXDs%_m4A5jp|97p%E1SN3?|T0L}aZDAkxz literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class new file mode 100644 index 0000000000000000000000000000000000000000..c5df8a5e0f107f1cb783f60f3fa55209f0027733 GIT binary patch literal 7346 zcmeHM-Ez}L6h2A;$4>YQQ2t60khTd>xwQ0;P^fVnV(OSQHpwv48Ag#du@YHpw5ts1 z3-kf{0DXd9cc#p=m!0Wr@BqEeJ|v@U5a`LKc_I6|-pZc2Ss}qUG|yE(&|OXq(b(bDOzEu5ODh=60B0oCrKi zI^3(aJKUEI$6It+#gmR)ZGtRE2Q)esGPSbR9Y#4M@N@n)sbeA1}Q%vQ^lC)1_o zq((!v4O~O|U>eitaG85Tnx33B-GKE|9=M|5ZB`V_UvVr}?3l8Jhaus_qk6eneOP}| zFHKeYX;7n_(gIW(9Vj~9Mu!Bmdu_MlaYG?`$LI4dYl~ZYA>V8rPza%GAasrO&^5x)HKNeHtlOLkUCp^|N~@(yn1(&;V4`t41cjm2g%KJoGkIhK zCtMZE8OEX`TfuySmH$V+7FOc^qJap;(pyS%(hi_u(0ELyQHHocI_{nVpBdqrjAbtb$CO0#q6Pl|ZsYb3BeRD3&F5cSu>v zvRLDs4+Qs0w%JKuad9NFd5WQD?RIn^NWo<=P0t(<*J%4(79m$m-!XAe$hgw>O0{x$ zaM)Ty#MN&wsOCD~GrMlKlagZ`{BRuS(+P*UI0bDv)5L+aiqdICpYPfmsh?QZr- znbKZ4Wh!xx-?8y}9xdZckR=kPRmktvWU~1=Y&j(85m$AuG|Ck7Srn=VuB+B;ir#hZ zc|fM2vXNynXNL0O&4Lu@G`%xOC+UJlr$)B+s#JgGXbojfJ!sD}zp1>Unp)g4-PyIw zy7usZY&k-sx&3K4p>1>;2U%y)Q)L{pW4$Gf&WzN$1>Q`6HeH)ly#+p|ls~`zV8sy* zv@fZCP(!{@pR$o5vkFYo?_=(4&LEN48LFOiTXC+c&E|T zo`?ooP}uFtEtnL&C?DYAo?uKIdqj%~#d$%&%BZDzTNNua&<0gD14fmb+sQ0*Zv{MP zNvdcKTdkkM#Hl8G$sSP`Sowela@{A;>{@&R5o(6X!1sj8U?tI2^&YoZH7YcC;9G10 z%|r<3`Z)URz1aNp%VWNq?X`G|7MrV7THgZ(a=tM%2$kF{xz5Hk!fT5?N(@YaxWq5j^4&hTw zG4Ynd1%834L+=3*#W2!*vLw97cVFUN{wE;{l8h$Hqr6K1Rnw>=2#6?*Oio zbP5b_OK6v zk1MBP@BwbR)P1;*s7Dg0#}cWpLb<+m>T5B!4Mndfu>Fw8X6%yf$3(W`F4=xcWcz5B zY(FQm-Pk4D^F+3fcgglgBHJe!*ls5H^hF}uXS-zkE0OKs|boC{L4k-j8K;kH%?&s%xVMz}@{9YWn{9 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class new file mode 100644 index 0000000000000000000000000000000000000000..fb24772344207310bbcab9c531a3d1776ab1b7fc GIT binary patch literal 10450 zcmeHN+j84P82%MPoVaPzrY)2fs2WZV6q!Q{r6~o+Nu~xjDY26ot~wiO9dDw8(XLYb z47>q1-0=cjFatAi&kOK24F5`Yv`OSjwi1|5bCJlB{{8kp?SEM9U;q5}2LLR?N&&_+ z_=fvtSx_#@J7kZPP0uNF(kYv^7g%L(?UpSfh~rt*E_?E4xlQd3^~(>0=&V!T@mx-q z_lO-3!92H#Km{1r;CzT?6L-73wzWe|p~0D@ zYBTC0>2zJfM|b#cTE6Q~roq+vBH_%e+JxgpWu%X@>p1nsc9d*}X zb|{eE@x3jZI{dC)oNBhIF1j76O8`Ua#E5jFo5XcJp>I*W4`S#wp);;C*J3`Z&F z7WJuX;tVa{bMXGV0N+`OPL)a%(gC2HT3%q3aY}POB&K8jZk5k!6O|or{nn|(zVOH1RWos z^ZadtpdiS|QETL(*nJ6s}Fll0jaJldU zQ+U1}rGV1BjP`+R(H3)MgvU4dn*=s$D5f^_hXEJz4N~dEl|eHCR)~sIN=i*&N5>dL z!b~J7!#QN~eEOi`RGf-aFb8GjlwnHxjZ$=W`V`6(^Qii$dal6Iqbbw$(LA71Vllzo z?G&QSI+L}5Lq*%ObQ#-AVNr809)3T;KV;Gj>j_ELFN+S=U_)3(O@tuJ*d&Fl1E3o2}!d)i!Iwa+h;iHOp1ZW`d(zXZqPN5ue z-5nBi5Xz|X4#GXwjFVfVNFp9lVH3kZ&yr{a*m<;Xs8%s>!Gb2#Ga)mU2{NFFbVhn~e+%nZBGer^~zEQ&NMc7UnxLD<)?5Lujb6GjYE zUBpWz0Z28O;<$o2l$V$*Mh!R$R!;9#kmPcTbMl7I*0K9W!{y4$F-&&#

Sd1*vEv zw|$0e3I&;f8F~!7Rw#&6zs(ldFNVMg68Mmo=EJdnU0rd*yTQ;kalsu0f-I?3=~7 z;d)*+K58qY!Ca}XG-M0OykA=naGTemHFuA3hRj#wW@?!Dkk@l{)l>_UFsDu2%&=Lj zj!Idj3X3U}r#&O~x2HX$y2-O2u2j!I+{7yxBUA6H(5HB0{(2xZ=Ya1pqn2ZT#;H5)6?HPrKRZV_|Pep z!J7WvV)tu`hjFIm{Yv1P@}Q9Ny6RychrBhsz&Gg%o@UYD`rxWKJQPxZI~rUW6b1@# z7nxDovjW`12g--e0^k9kqMRf;WK@7Jkw9_j0xW89L3t1@@&YU!INKC8b|~A!Xk(#? zpKdtZ06aZ}XRKa?tN1s8zs8^dli_s=isAJPOo!LAa1O6U95Dmu@&5(9yM?1Q`Sf>~ z{dV@!ukg~JA?+*pecnx01nXcn6Nu}epk0!Z^&~7B48Hs3b zAU`qMn+jUY+gk}}cN5Xx*+(lPs^qPdh&Z2sSV=^DFC*g31jO}3L?a_&IRUYii1=X+ z#3zY}ALT&&ArbN89Ed+9BHqe@_)7xf7~Do*8G}#o)FL2u0X~J#U;#ddFQ5X|e*xW| Bj5`1T literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class new file mode 100644 index 0000000000000000000000000000000000000000..e0990c6b289ce5792e757e20c7b8288866df0ace GIT binary patch literal 8305 zcmeHM>rxy=6h6Hn?8OiwkQj5F#FT=USuZh~fG83I=mIeiVp9GYcADLR+3C$p4~F;g z9jwwS{qHmQES6u->}3{acUo(){6SI8ZqJ-Oefm3JpVNK%kH3ETjfj@$d7g5NUIc!% zEO{Wyd)9$fu8KxEu$twnD?+;**!yMMl2$|5+${_BwOr?Jll$d0VLQ7{)sl|zp75Y4 zynt^(%^me`V`q<7Wu8VDU5M1WmRBpsPZ<>>MO%kW&S;|2eZMJv$E(dU8e4=mX?b$n zazkFABBN4QEu*VXLQgskzUmz~fwSZC1;#3fBdc z0GZL$^i0Cg4qC;^y70Wvb)jG?A+T}4ec!Q_-jPK(Fi&qX8t=lrTZGyi%{`ACYt!z?ME|AHXGaNKD0E<~Q7_g$2Q0&+Lm!^lH?N6kes+ZFIv}WQomToHDX$RrjqL zH*O;`nn;Z>8mZAlYBW2k(Tp;h8K=hEhAntts4&^EWVLQcz{g)T0Y*|CGK{J6t{H)< ztvtrbX*7i|V}E##NSRNMTuXbFnDA>R9KwCMrz6@B)qMxB9nT3pgDb74qM8UqWC*3> zW}zDK*o?AqoUApxV1div;;CeE|8k70dm%P1Ssbe$^JKoH`jhZ6w~V=Z9gv_CzgGJNRSd>4CoWOYD} z?^fty-E^f*V=WA%dIKrL@>CLx6(@vBdKA>$ zdWH|Q6sb*Vi%qh%X25>2JD4qIw_0H|)EW@2v$%nkg1uXO=*O`+T453K;z6{|hg#&> zwi|H;VXhyg6GqNwK&9c90TJaNI#^?{;fTbMVF>VOF-c2EV`9V7qm$B-p8F;c-hyp4 zQCZ7i3$`*D5*>18$Z5@0XRRm7Y{M8*E$<9H8X_5aI{`k5n(gSr=t@+adR6Z3{mbnF zV1YhoxicCk`eE@*2YW3ip2k_!Pj!ledT|!@R}@*WWj#`VjrVV}GcOq3Ia)-2Z22hN zVR`pd)32tm4Yg)fp+(Vip0PiNLpKdZ4oBoP>SrBfdIJxt1yQO%463&dKT-K`6_+xb zBJ`{L5$bo0uJ=u(%5RLEN+;@{cpZAQqRw`x)KB7WP-8s!sjU7zJ|{UI!9x8dOmtL-t#U;7a&KF8=*;>f|UAY z37;47c?swPuex_@`qn9HC|J5i&%dmwoP9#OQ=`HjS(K;HH)Z2J= zT-!S-+U8TW&Ct8BBc8>3omuE>T}ajXzJ`q-q}X^bRa=>EwlF+8;~ZumpK9ZMdH`*04l1om z<1AX9fkJcm(xySA^mDZF EH*PK=`2YX_ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class new file mode 100644 index 0000000000000000000000000000000000000000..1008ebf9081b96d630d2de7b4ec4d04dc230c2ae GIT binary patch literal 5856 zcmeHLUsL2n5bxm1vI`ssdj31li+C!im-yh5M^Qz=>aDea!b+9CSVJ=G4kRBo5^&3^Xs1Oo=*S$&+mT%Knp&v!;Ay_N(R2B zO8ci|K>R?&z9NYqL?R7+6`uJa(IgfjjeKEl{XUHnD*as%vJ(~%%>+N7DiK`KF2dBI z=D^(yDcB}i;ftAi!-StzC87ZhUDP{aQ3mTKQXEG#R$sc!Mz>F0Jxr);06e3Smuut(gmammtK|MV02XjJ;bi z8eZCp>Z}%t_*k5ITT!$ndn!9NWg2K9t2S@+;trnVmZGg^DT&k;51$g%HwUWadtXzn zsV$#9@nU)$a!SsI-M$n*D@$Hok=Glg;_WzzSb#C0QY>bEbY0wsnTW!rCh?zA(#Ufgqn?sOo~Txp~NZST$D7$N=$WMgs#cC zYul#A*eJZeS;F2N(OHEvsiJE(3J4VpOltn`B35Rx63X4Zg3(Jxnh<-%jkGYMm1Z_! z^?$5uWl<|3s|W8q4-9QDyO)M}D(0E$2B3;}+LU4>u-dh62gW3R78-x+XE-aDy!tn~q4HV5eXn zE0?qqvQTVVI5w3*ap%5Kf^UT+DK86(l2{Q|_|t`RR>)bHV`8CFWHc?CSuJLhds=x0 z*duXSL3U&6dxG;cD*N;*LYbsgVQ>qPilVKsU=x<$^#xdfdk)-NyV^1`O`fF=mRNgf zd`xARVBf~{mPA0JBO;j@j~>=^pD72v>rA=dg_+zll3fdidtLdBP^cL)cXha!~${R&%M4m_BcEKQ_ho3PC}m018#RfjbP zew^}#Ju;R*?@BhugF1YQ?ZrZ=TZhM&I+=M2K#IFeDQsAFu`P&OIlqT^yVUrf0vE9N zh~3p0cn4mESsc}~(HxGOFpu{&ycS>)XYSzL7bxxEfAbGm`e9}HH+bzY9L>NR_-oz< zyxzp$g(H@kb?U$lGd_go-isoLRHId`{$gP~;Y6-tz mN)T)C2r*{hL-+{)o2c){c&$SNJRHyB%qQ>}_;_D4bN>R{&6)iG literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class new file mode 100644 index 0000000000000000000000000000000000000000..30143d81bf12c987084756b1f0b8fce2d23ef19e GIT binary patch literal 6853 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class new file mode 100644 index 0000000000000000000000000000000000000000..6a863456391c7b7d0a9a1168fa6c7d73e2af2b8b GIT binary patch literal 7895 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 E7kg4YDgXcg literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..08a4eadc3616d6129d1df629d779ac1b4440da9f GIT binary patch literal 6260 zcmeHLTT|Oc6h2D`!Uh5*q)n4v7A9>;FH-N9KxsT+63-Zu28WsEg%Q$X7NlKgwPK)u zEHmv)``#bb=~>AbIha_Piqc8)AZw3x^nGW~?w(zJfBpU29{^B=yA}))SdEnDXclQ_ z&pmJ*Plit9wjD2!vF}9wzT>;v4W-WlN1CRy%Yrsj&iV=(t31$5HSui=h6zk15P>UN z&ieKq^E83+jpoBzjitW6T&Z6~ zEOqI*K~F4GX)M{4gy)`;B#dr4O0`DwcsTaYox3J9YcZ7!#K$FNCpSYr?`OHJa9Fag0_|14pf2<-+F*mr-zpk9L@1!b64~B}25I#Q4mowimcjM0a?= zqW_Y`XGm-UXuICN+hTMMdv0Utz<{uH*jPGjQ!kRzVJD@-PD{re^(Bj_>2v66Z?C2i!P6Y@uq~CQQnhT%4pVxsPnvsN6BdmN@nvy5w3B(2&cxGm ztf%uPPkS-W_(7e|?bsQwL3qWw1v1>0`*tM=DykJNOA%=mds?bNVRT-+$24wdeVsGg zR-floOX?=eyIe}QmH|%r;qrU-Y$q4M-?N2W6u;R!E`YnUcNE5)YYLy^tSfjD|G(k# zFR@(x$mLWp=WDp`;VHW+&Zi^}LM@lNrLa#y=sZ26P?bvty@uz=Q>VgmDgP*U12}^w zo1{U$|E@a;dX6Ywuy1D|zDSO8*6hPJ9?K&<&aVbB6Z(Gg`P+ARasr-w0QW)34ov=( z@3+vD&C+&~#@{sru7pxJ7+oH77PX)$mw+0hc@tLzY5+)CYRD z5A-nH#&?F`WB3H`)7bhHF>NS=gZD4)UWX<8{|eH6iNSvh-@_ePgrD%gk;K>e2mbai ANB{r; literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class new file mode 100644 index 0000000000000000000000000000000000000000..e20b161fe5322499de2292cecaf31b402bfad8ad GIT binary patch literal 6709 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class new file mode 100644 index 0000000000000000000000000000000000000000..6af35c90bedea6608c1afd88fed71922212c37af GIT binary patch literal 6709 zcmeHMTW{P%6h01VHoFR>goIlQn2^E-koScrQdMemQ5NM=HJ6IVi9NeJiFd}#jHBjn z@qz>rh*y3T;u|{`?`D&|q$MCl(b}H)%(;AXE;Gk}{r&6jM6^oZmT1hQ(CDycxv{lF zcFby_YS#=)YGJI>sAi(0TEr}CtBA)nFK zVY#^P(SsK$^lbCGJQhYA#Qdq0$}%gIG#)+NxG-!dO%lzGdC9aCvT2~vy<-L>JX+{x z@|IU{JQmp@r8_p#`?r3c-7#V%McP8 zTD39$DaE_U55kxk<2OXi&A%xn;t=c}ndVc?=6$N(Fdd5(c-CrVql7(r9mR(j9_ELQXAz8tq59;I|MI82cP`?=o0gtza_2=AWOyhJM6RN;{;0zC+Pt9+Jaze>5@ zPGS)P6_cWP0e@+%PD87-pXbBsqDy_LjCeyxm+tTe&n%5yJfBks{$>hcIhD_(%OE-9 z0#T*BV>Qx$ADvuoMgeH4$ZvAHk_+46`dEbgIkPROlqFr4sIna1QS9>EY@Csfpe>AX zA~V8V{77o#m2_FfF%lyR+|8BP)3LJ#Kh!$e2a|z-8;IymY)5e~2E$O$3`IQ9%A9)o zy0ys)B3H<(5?fK{XB-j9R~ZWTN8^`tp}b|DG!BnA$6#u z9}L1YN4`fAJI@|n8}>Vi`OdjeBsx&-RS;f3me^0Tcvp?Gd^gaxFjYv)n_Ta^LrS%t z53a2{K}QvOmWmiB!0U?9DO2oH`j_c}W-N0l9w>X5IYoeGIvhG(rv*ta8^I4Y$YB!6yPj;_a}THGb> zdYnfU(h2Jm06SNgu4f{QMY&%`wP!kP;ak8|T}rN(U4NOI5`BrU10#MM=rRngdZ)!z zm83+AaOBIlLZi*Rxw4B!Ivjd(3tZNfvE-9SZ{b@C=FvTxz|$E1N;HY<6h0{1n3-_4{)8zo-6breT30CeM}EPRX(M9i2E5g zHz()zNe^y6_uw``pJC1z6#qQC&(jyU6D{CZfn1Ls(N}o-8sl+{s`L$>#^}*Me^L%9 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class new file mode 100644 index 0000000000000000000000000000000000000000..47dbc9bc6305cdeee4a41539fa4362c974da4a2b GIT binary patch literal 10272 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..f7384f72eabe350ff909c61fa84ae40015db6836 GIT binary patch literal 10874 zcmds7>2ur06@M$s;t|3K?boj z4n=a4-c6hTCf_>K&a^ZAnE#^xNTV3y3akoeqbCL^|t8NJ)>7|+afUQp82?L8otpLCb#QCKGyfS-Q#Y3YXvVm z+~#e6^{8h!W)nDW$e&fmxBTN0jWe23IBmn})VEp(0A*Bd==!6zja7YRhf!NEX!VdV zqh<32MwRB)qwU7M)y*a&QT&Qq4sQzl=$7+XuD)Y+9K#P>&gk>=U45~r-@*RTU#rkCJ1V%Ngxm3sjYb@_&KXn7WP zdBbspZ%B+Dqq2U7`#yKU^|Z;ntY`X%k-9N^M zNnJVkzV%d;@0XLH34JrL?Z~&5fiseI)fdbAmTiI&MiaVjc|H&s>Y-TqUDy+X(S?@b zGAgwK(7^H3otrO8n29)dJ6sN&UROqQR5u2;Sl<+m16kqYs|kzR33@%3d)@=Xb+A9A z!A+5th^}`_t5)PZt*t21T3j2z`$97`Y5&!V)KdmXNZVNe<)9}ANa$D2iJr4tESDH_ zurEQ5GPsY-@^q*wy+uEmq?>dL8+OrhEPsj7>|D}RLk~W`3mZiE<(omb#oead{L*Me z+pu>H_!PWHgEJi-Wq?U9e-(RCl3iE1U=$b1^dm-Vr(3Z!;?SF)&Wd#B`xc!4EY@Ec zar*EJ7;h?vKQly2-{m`_qs2O{2@td1TA zn&oMhV_Gg!1p631YmXzOwBhCUT+zk*lK|gLt=5*&THCX1?o})2w~EQJuq2>ov>zKC zuDuTLTSL(Vf>1QoP&CyBMN>@$&RSSBy{?&pdm2&|t!w!0ea(kx_)!mxCefiLs>zD1 zrUaH7c_;&`+Y_#@3Aa;&bP&xCQp7w2s#Xbn%RW=UqgosX^B{g6c*)#B0C$yzSjukC z@Li(GqtZ*i;Hh*t}=ib z8cIjYgkiTl^!oyzE8CS3ttD}*fEFbY+F~f&Cd}67j_GMoQaH5u&eD=5_EaR4^@x_W ze%ua9_SyOiELAJZVYOp9NE^bu!*Dx67u7X#1?;Yvl@m|_$NzzJWV7tQl!`(DY2-4v zk?oY$A5Pd&EPT~th)7+U3m2OY(>_mb4QWy^R1c+csLv4OOHNlVp{n8}W&b8U#&Az3 z131~`$pDS}KN*1iZcqkTF86nOY7?@37!{l_fRmDeq@{BnpXPna6& zHJ+U$bDCRj^Ubvpi`r!W6K2BnD0aXZx(UIRm(GA3MD+}~5Tgt)8Gv~aCR2gaQ8M~u zuBfi?LfG>jZFG%}$cEa#3MQn;_SGmyqa%_>oNR>qi_z5t#MU8qUCZPPWqQD9{q!c1 zYysx0JJ8WWa|m_iGhNIu)m)f=A16nOis2@G|1_gaKV$S>HZjjx-9QTHz^h4k+>h#A z)kNFCY08cW+%{iB7Yj!S>DjOB&@kH52c=Dss>O-59v^B<=rWJPVZ@9+Qe0y6sy7 z9Fe%~{bL+cZ8$hDQE3wQN`zCOGu_-Jdcw#m=syahhe$Y#jH{DK2PML(pivMBaSVVe z4(h7O7uHyu7Q^*(I1A64%Id((I-Ry%jvu4$-7~RCC^CY8Tuv~(CfnU={16@!XJhv z=^9<62|Qh+5|uGp!SfhZ@%{q7_TMkz{W4t{yiei%#i92pnx+}dysGBU(pPYw0Q}3i zPXjvscKw-euhLgBFI@L)L*K^e>!bmP#8m~%4cxzhYmDY-elX{a!5sO0jJ}Dr;yDsN z9RF78_+_m8ZK`2jf0a6Bdscw;oP^_b>GQe_(Ws7Vs;%BYH%OYJ~9i zLt2XP$ZvfdZv%&f3~{`Zg5yJlLrPon*#OTH+wCui?P)5uWm*9pp_Eq#63b8H$EjG? z1~f|jNLi<2ypzK92dNl0a$!N*tEV#ernd1Gxb|>$a2?>XaS?TKy{TN6LoZ=&54v`7 u_sF4OqDqIjAHfM7?BCJ`!rO~0kzLkEAPKVxK!oc{n3b~qjY literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class new file mode 100644 index 0000000000000000000000000000000000000000..d0b28bad0993bb45cfbfd04cff721c93b7fd069d GIT binary patch literal 583 zcmcIi%T59@6g@?q;iaO{jceET12AqB0}09kexP)2pjg^TTa2^vzg+kMew6VJ?g&e5 z()(!7J-t2c*Z0RKfC0{PGz3%(S|*A~uGFiN+OK6)rPS7M3K@$RS*WDez91`o9m*xy zl7gH(a55!JIt|`R<%$_|T-Rpcl1bI?mYQ42aVXH+vRmaAax!0WkwAOuH$l^*u_SP{ zm+z*o5@=1$!l|?g416E&GWEy1C*F?#=~Df>zQEra1p!v)%GxRZKfWQHNfO=#&V%uP!Z+#Q3 adKvBQ6pZZPIBTQFdB(UCt~}$ofzA)ZuCpQl literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class new file mode 100644 index 0000000000000000000000000000000000000000..c2f1a8495cedd0786d3bf480ba487aea0111e186 GIT binary patch literal 6340 zcmds6-Etc>6h51_aqI?K(jRDnB1(beW__P{%%mYDVO+OEOlG*0cV%0NR&ph6CgH`H zff=~xVR!_FBW+r5h?7;AI0Y_ty{rB9{OO#tpXA^F{P|Y^*o5U8%tbITI%!&FY;%tu z&}O2trlI3zB2=C>O}gJqsim1pnP@6^*&H!3X1e*}YrNTEf+cqIa7<;|Lm5xpX;WHm zkBUdMFW58AL|TLE5#07F1eL?)i~b(IS%diq=*%sMV7|pAx9te-Eq5kH>{!j^aBXEb zf+yeQXm-Z7Yc8VG8bYkg6kU?l^VPg!L5nNO|6w) zfLqZW`n}Z5C~?42f>9lF;f0W~RwzC*tE5ryjTo^QcKEEu`^&ylYKg5+1Za@tt_+eJ* zo&yh+8VVL7HXMnW5h5@ECREl}`|)~`Fk?FGfK~eR`VAr0_0Vi!d0U+)R_Q8>j$eG5 zbCI%8wCk3Ef;V5~RG4*{Zc#IGJtpwIZ!aX2-2O>=?CJweChd2XzGf+#|z>l%gN5~xvkr98zB zVilpj2RG^+j&e@dV;NG+{bhgx>{=w_##5P9@`mP3CC%}KG`oi9C=ewFRPYpA zW8WZ|&MouZ&vBx@lkF+dF+U}(BDg(le3r@75S*l9sP$F5P1L}1DNi%2^iN3p3(0y4 zPaQ7zUn^)g>Kg?uiVmYb%(t_M zW!dG6ihRq_{BMLQ_gB34U# zrv<}YYj;X1)tZ${YZD)4rD8&vFiiKhrCYt=egHDtEzUI6$>=D8#ce5>_Fr;lYVb(} zmugM~cc&5P2p&uu_H$B$&my>4nu8jAeqy7&Xo+b0^Md8|Faxmy7P`2Lx}}T-0ufxp z;|BgI5aEI41anY#R}l_0@Zu&O&k!_+zl~pD@rR{%e}db;;oUV@!t=a;F@igIz38tS za2M|3-Tf0I?|bYAc%Fj?@X-H$2p{1uz{hwj;rI=B1Z2K}QXavlsQn8ZUqC&N@puAX J;@uoP`U_0bmMH)L literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class new file mode 100644 index 0000000000000000000000000000000000000000..6059f017edb4f8b6bc748032da534cc3761c1de6 GIT binary patch literal 7551 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class new file mode 100644 index 0000000000000000000000000000000000000000..2eb3f1ac826479b1b78e68a8b72f32fc34ca69c7 GIT binary patch literal 6827 zcmds6TXWnr6h2BzHtVJrdVyY`s3kyBz$Oe&Z6}>Hq!4DCG9;Agqt@1Ll*rQ7k~0Z^ zj2W1LcYYMZk)70L;>1cO+X4@3S@!4e9G#nwwSWHg>+b-t3}1F&fq_R9PcGGj3qOM4us_9bxKJ%3U&&NSiHrCoZiKihv*4Z2t5&^C&E{r2n$by zr6=yvL~}#knp0-u4QkOed}o9Ut<#~KL+9qOFqk)aX#RHE zQgSQgsRDPx!ntKQ9CY*n4UU6%i`&dGOxrYewi_^GdA5_bF3*97S`Q`nq4h^%WcUzz zz@$$5dMjLvV{S~3Z}ZkQ?LQ&qVm35OS{a+=vDI0dMtcuF&V@|4FWS`^PvYg5Ig@5l zCEqf$;cATMbKh`_-t+{o>XWT{?Knc#^_tCu8aC>*l8&T^F;Gp8l^9E8<$$sV&d#}V zm#c&igmNVgU4!Q=ml)g?I;2l?W8ED@QKnqgD2Z#JpzBh(K@Vy*Wkn4Rx;=qB2lKHG zDSdw$AP;>!Acxxb*h;@vVP0nR(8xh{TD)<1p4M%2NPgBKJ}FbjFZxuL`U6L#OC9D@ zAL{t;j8=;Y8#Nb)Iugr#i!q-k>9VrI?zZ4uV^d2%+L+Nn_t276*UBr#y_h@_+)LP! zmab#OZq78=A?QBVShZ*>Owr2Rr?1+)#&$DwJ?EZPvEaEDh|z5(MS`WVZ_wJgzyb|5ZsIQr6srV#2y1iKAnZU&V znJ255+zqv*UKd?m|31^$JhhhNBJYb(XyIJVrF7}v0`oH6(Y<)Nl5K_yMtK--hc%Y< zB_Ega4O{cSVJ4rq;+W#nGu*tx*DLSGm1J*ZF=gLS)%%vjE#W?u;8hdh#pvEA(jBemc!9))}|TG8j?CYSMwb1lX7)RarvdY9+I}+<}h>48|_KNIwEMnp7z6bzqhN zAG;ndX$f3!Qp4Tqz^4S#v1?J!cLJ?n5CqPxV(dKSE~POtBLXkRq~pwd2j&TganVhRgvaR;1#iP; zoB>?HyAyB~&m8I+jtAM^X<(y`Y}Zf7Hs8p0712Qw}M*si- literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..678316cc6e5feac0831e7ee3c08b7c799d8742ea GIT binary patch literal 11775 zcmeHN>vP;R5MPHTPA*B)rsdfJZfW7lM<&h?#O>xsg(oo9NXDfG3eAd;LL&6L* z{8!At418ty#Ap5(hM#0#;>)Ey!JG}uZe_4C9PJg{q4^mew?9izTs>+b(gp6vD2x$0Z+Vo>}}RPM>uWnv7pZ7&w7gm9TwGZETFK$BHs!8Zyn+D za1~6>$$yKX@WoE<=jFr?JQkUB#GqqJOyGpg`i;g0bA>@uekfRzMQcuwFoP!SrL=s} zpi>_wVAf_!;g%o!jeyOCAs0$qY|v?YkJ(Dn=|n7!KXIba51TO>k8Y(5XYGKu8+@~V zg@r6~1oKci(M)_VlQ?GIy0x?b;$$o$G|tGgdG@LTkQ01;J!S%k@bRSIu# z9xx}&02B7*w2Z1se0fK71&`UXAgNe!FTI|@O?#!qBdIk>e*M*q3|;cD3F_D-KlH`B z20c){ux8Mad29xgo-=4>Z}lxk5s${{1>@Myw?ZsK9&9ns!e;9Dfm*FrC*sgRJAT`$ zOsuw;C3ZT@k_>8cFVxb#V7ZQ5-&TWJT_!baNmzbt`Jv}W*e<~is@55%+l7Snh__L` zo1o6KYHr}fv9<08EG|+aNKKiMT53kl zS{`SyCCA)$gxj(NRKvDA7-*gy_D@aPsG3rkHuF>me!GL|wRqI5K|3rG8waSlBxswv zo4(*W9ngd-jX-y;K|B>u+R{T{wm*C3T&X7!exRkEp#!&TYUii6qTdSibXBcnY&C3^ z|2%Rdf$bdBO}U}homjAFyFF0<#e$ohH-mwCm3{bkkua`0TK%8HQopS>=eqFhY_`P) zu8wYtAef7q@jMUV(Iu|n(I9PQ4Kr6&6Ad54(S$DEO`IT}3%#pO+>$PYX1gm)U|(p0 zm-#;%^~&E7vdUMKGkjp5?pbBw)N_>AQ<;#q_JG!TNID$@Ks`S90PL%xj zKd0k8a&*>cO0OXNyNCzl$&1nX&zlEhp^;i&h;3H4vv!=f;|YXducN@rY>o2+{?G1)lmNjEL5JQ{4?8UMN@c zQCrT}&4?pBDt6G7$CUcE>w7o5`Z5p0BnVJ>ZhvGG(L$%qFx5RF&WgPZywvK&ak^-X zagFfOu=e_)d)0MVwXB|o+7I0;8Hc~W1=6`4XsG(oy&8(958W%R>#D|r)UqL3pfcSn zgG4ob9V_)T9VfiEoVov5hnEzCzCXxEDfis}uDb8>uT)JLbX@rz3k**i>M>S6aE&H0 z-Z2gOwOW`iE|xq@tV8v#y&*^-5CRlBdhB@5ocMD9K<)O`{!dF0r&>dn1mu}L+1)Gt}CG*U8apk%S54VhtM(C5|S1mjdePD>hX zH0UHGNvEf-GI>%e5GQlQ8(S=jd{5hSRd^;&Gykw&`EH8Q1O0fpR9-Jkv{yR^jIJ56rJv)Jx)*b(Vir$k9LO6 z_R*fAbA7b)^z=U3arF5as-pLK4P5~BRcO$_xAGG``|0fSKTz!#HNKbdUuu%@_cH!V zO#(%)poOH3(raibxmCdR4fQ2@6JOIn^;`JSWj`V-75ldjVE@N~>?i5c-)H}hHv4IM zuSoVY^nS+vD86$TzovrpM{u0xiyXlTS^)cWsW0|TbN|fhcl=@cpvaCtN>_?xe~PYV z>`Q)Cy({ybeTlKnE>$jorwaCUdpo+;MfQ8($MjH6tW zALp#iKj%71E8sIit33zCEzo8`U8CD{hbCa>*XT2Rw?IEl)ATugNncZgJX)s~ZBRfV Nbtoc1-zaXk{{oy6`$PZ$ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..c5fca2405d89292cc0046bb99ff7b28fd8332f5b GIT binary patch literal 12085 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class new file mode 100644 index 0000000000000000000000000000000000000000..393104af8600309fa117ca79d7be5427038a58d3 GIT binary patch literal 6142 zcmeHLTW=dh6h0HuI5sIQr7h4>7)pVJ%Py31YpY5$ZJJ0wE6}<6+g!pE+aW={ZZzabFi3fXcXU;iuF5fw4e*NpmpNZ%$U2o77qb+OV z&T@(4Y>)?CE|K%SVG|lLAE+&msH+<0960u`+!3wh@WASz5dP~~mv=7DO z4C@%3Y40ysb4IG}4MsCJrIPLzqvz%qHW*DWLcaz*&*;=X@m*uInWdBLWb%zN!s>KK zBpmx2$TU}u4~*`lqHnMBR&%{4xZ52F?imDLy`WH!$B|On@s8koOai{*xU^iVL>lZM z-NmO(VT6hyVbkb7o*!rUmhd1>BWwAlOog4Klte(VS6~pu+fi5W%h-7U&q5)1Hh^b? zp=X2Kvq9n6%RJG-a=+)kcPDo+jd(J^M$7CFHh~YDAU9ZT=7kMp9xdJ(L07t7)~T@a z|CGmQrQB&w_Qfc}=Y*M)=~xM$1bI>wX?Qrn4!B;MZm^Xh-A8F~T+xK2sUv!PWU20V z^mcGhI44Y{=tsboRUL18XssaNPV*>-vsM$poXQy1vl(C;DW|M6S?sjo1;EWOcr43Q zA~vP+_=PXHkFpf3_o)NEnn74W#Ute*p9Ud!c7!o9se*_ynXjCE;d(mZUE$Vy(jubt zW>HORcL~9;OW=JUXL?UKq7gWVG7VcHsPib!3w0s0)jY1vbVOIG#U45Vu_oMP_ms!S zQ1-J1%5hm_Ebc9KWd*v6XY+FntgE;2J@9;(DPI9s)TSOs=9Th5=|znuSox?qgsr}v z$^*D|Zq!88k^*{rT$1k;m0;Pq!5tJEsgA1IWk{&KsMTDpEUsl{wV?UPUcJnzE!DP2 zmLuC6Ew!!VD8+~)){knE%2~NL1&xTK4J|Q&9GWM}Iv(H}`7*s*v7%-~)R0%px^;+J z)S^>!)S%Oh&dnbl+Wee$3LX&mvwlaI^$4SvpYFV!HzFhb^V65p4n>4f+Me8KikY(U z6y6xA`Gv`fzBq)U9cd*D@s%Gr^Gt&sn(e2!1|`py47*pKZ_Ls~M!(k>a_iya9^Z<% zD@SSxZpKm=p0OSTv3{`eQ|f6x#UZB-aug7??{;Q;3>yyDyR zFhBDtpahZpTKQ!*7~pWlK8lRO0guJ!6FU*wV-FBT{~q2GO5jjQBrx|ojDD+O?*Ha+ zz^JvRGZTv?oEb3+YPSjbNetj(O?FYGGlRu{wyP(No0G)13Ch~f$y^ja)QLAR}qe)!W*3p}U(^;G|LlT{!8Qe|d zzCpA2Z{q11zA{`}KT+%3Gjl)C3%}rLieAM3O#a2MB$_Lz=p{Ofr)fGz=Rx%{?pwIN zg6pgJ9p-qUg5!JzM<5Yhs>Sj33XWkVM~>YWI{KYlsnqZ73T|&zaBI-JSU*Mcw2)ul s%lGd2zK`b*@FV&VzcZi#TF^(Z#mBgQg6kJ}KZEzz=rj5f_fr)74Y9rrZ~y=R literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class new file mode 100644 index 0000000000000000000000000000000000000000..cf47d62fdd9ccbb4bc7b9a0c85130682e1c41c86 GIT binary patch literal 5301 zcmeHL-*4kY5S}f$rZGKwlq@+vAzp*>Aqt@!$Xc{wDxDhp+0eM8FswN0u8K zow0Kk#VU&o8%1%d@+30JFiMzZnM!yXDL0G;JRNZz9q-}I>DT+YjIB_zm5r3P&zOw| zEopgQGxxNEdY`zHf3Z7Yvd;~-b*K@z=k2BVC_3(*@z@erek!D}I|S5bdvR`UZ=DjT z?cx;c&>*mO<=1nqm0p3j$=&2fsku?S$Poy;Q{9sBkT)1$h%Xb{J-QaM#(Rl6&arucrTb+kpIEB0t{R5lb8EStYWgSSmO~W}9Xn4EpjJ`l=`3g< zx2Gb;=$RauA5%^mtMk|@O$&nC+;Uu&%Y^rYbo_;HaLn=)RbNSm^e{(bg%oAdQ9_}( zBos7GtQN9A!>9QPIe5-6Rz!1#(EL2Qan;I;;w$QYnsaFnd_{i7jEEQJNqi$b(p>F;)m_ zk78e_3!6dXwkFf%eIbio&k47Sol|MYg|=T8D7O`nxh(HwANjiLT!HJVO?*tAKjhL? zz?ST(d1PKG52V^vxQ~_&HP^V+ZKphdE9XW3Sb59uEHI7hrq^W+ZE=>_lL#DzE4}j72rpC*5$gx zu=OIa?&EyQv~c&A4{H{S2!VWoZRCWNwh7cPd5*xZn_F+lhX)gKIu}M@XufkzsLwr& zOrw3tbm;B7aRE)XHx3$%5W5j%*ojh1xVd|_NwI}ltm9S<%DthS`&de{Bq z;HD0b2%IhM-%lML6L@jk_7D$~y(tpbWIPE_9lj*+a0W~;RENSC?2S73mkD!t%=ifOlaX z?`rTK+{f=Wa2(+EeY}2v$5^BLuF&}56pcGmG%mx(_{|bLfKUAOQ}_(801xq4$LAE9 Z@B|v@MH4>9>n1+0;oTRog`*{C{s(&GAOipZ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class new file mode 100644 index 0000000000000000000000000000000000000000..a0f474df609d1a7530470409a7b5a4feec9986ac GIT binary patch literal 5723 zcmeHLTW{Mo6h7)Dj@|UKbltjj7p`l!bQftG_OuQI(xh1oI7!jEDfZOS5@R!wNspw0 z_LmLVfDPE=e$=p!QtT+L0;_=W`Xdu#&Fwxd3I@{l^Wh!=3tM+iDjeEdd zJZLK?`iA?_F7!6&LO|DYh zMyme(fG)3SWgS;;o2QwWrzZ48Bkl%~(~%WsU&bP!o3H45u-si)FSO46D3x?S1$2Jt z-gZD2R^U#9t_F1V(Aq;|w3(;3g3HNMWrWq~zDO9tF_LNS3mX~TOU2Osz*^1jKrr`m zB$!tadcVTLFBWs9v|~NN#!5ogc1&6(RU!>sPhaBIjxa*Su&`tF5dGgWyh~UZr`%e$ zBU52d(@G*>*iT@@<2~LN>^3|P0R&74KnMYZZ~}xd2SQi?;WkUOu*~~B^sCPtLL**` z;AmMLP!@WWg?YegD=%Uob8z{^2>a3vvR*}$&m>PUO8KHWITX_Zp9^MArZW|M5$2n! z*oLofup>*aO+Vbtu-&K4;H1!mO;gA8?8H(%?CHJmXW^VMm8PG8TQ+pO=jpgYfH}>0 zPG_wqcs!LcqGvO(JhmK|*JQENh7|<2bkB2HrV_CumFF+KL7QhOR3A!*Y$JoQLW*(a zDWOnY5^6OIj-N88*$H`gPBB(QbBfUHW_0YTl@}#Xt82)8%B|762*);?5(9?E%G*wEulkBtM3;6bEm z#0sF!QJg30!e*;^QqJ^5U#gV>P6FbIaHs229*BwUHwDT`MPw$+TXUB=Kl0_+c*Vi!P<-=JCHSaT2B#*Rp?L+oQF}l>L z;Ensq#U63Pk;H^KBH!#Bu-N^1V!`=;>eQ|< zPyv!Mtj?J6;D~a@jN@C3GCaR5;Zky;|i?K&>Y_GqG61B~Ux)+c=J=>GBBNE4C5|hCOeHGC4 z3YKnAc;v#eSlNIUHl($3gw>sM9%%O5}d1OV&sNd+bd^oF9*)j72)%Oh>_(7!N&^O_cA2Q4OD&&!`+)uy9qXF0c&O=-r+}ns{V1<&+{y=H_ z1-B2F4SG#!c~>*{Vjo%Gb+_b21ttkxPb5W5cKx09ArCBpsry0-yGr1>g~bB`lWQoj z0yP5DPiWV*R=Nr=kjA%PNzILlj(JFN!vhf|qUb=YcEtPUGg`0h_c*mD15OI$U!ZpoETAeOy0^@m)y0AY#VQj)=G^Q%cBD zu-m|Z1xKvQ={#!gp{sC1=qe9g<(;|8OI+opu9~Nz;)Xh%`_4JkqG|Z!0V-*p8Qu{-_L3AxljeLnj5tc^u6Gz? za*ktsRldg%FCzLMl6{TIy`_R9hg3P@s8y6Dcv7z+)I}mdgP0hB5My2%tK+~bO;dzf zSae_#%aC`3bO3^H@HdMiWc`HNqgycwOQ|T6E+IU9v^4UZ6#$K<;88*=sU+;YZ3A>8!2Q5R~q`<_Ebo_ z-0pFGECT)|v%L+a6ZLly>@g0|kE)Xvk&9G2@2uLPxlPs44b)!j!J`Rq4~_T_Mkc@IRv#&tz8VN)^@JIDp7Q=^K z{bl%MgfDYt@+60CLF2BeD8jxK=L+-pnG`A#MTqK9g%=y}JX|9%x6pE}m7k0?>BlQ> zl$mZ}53n8g+g$H6oK6VLv{b;N1Ez&rpZ+;%5qk-!R;lCI3V~ZsErA4P7Z%SeM&RaI z<{k?pP>;{uP?dxvs!?m5!T<`G&3rzr;wx2njgZ{}V_Y#f8S>4EiEK6$@o6$R{%gAf z0u}V9tK$S7q36ntrYhVb@N3y#%>eGH?q%0S0@p^njVe$Azdu7mjBclVdo;3bN)_%9 z`0W{5BfFXMZBgG-u@3kKo){5WzDUQB9K|4-?ujl|`&e^AzH8+VX!lD_^^(P{uC%g! zL}G*Nj9Iq&t613Pn$ivG!niJTQ*h06&^sZ}XiCZTnmZ=srULH~2ugrq43o3DvjQH0 z8~K4!fe*3hKh>BOxHoc0o0d&fY)cq}XA%2&tcvxex{Yn1wh&Nd`7EadwtE zet|#9Dy`D*{wT|5b_pa9i=e=VD?TKd$@D(G-P8T_^!)YrZ+{Tc8u{8;oTsNbs7apbQnb3@{W!~9B4NT+9K`>6Kt&q|KwpMqxt6TM#&v$G1vvwx9 zXN^>?C669dv`Q>jcAv+on4%)^f-bl1!0I3o=17LZqbKL^R+j6PjU09IJE^4o-lOsI z;=V@{6B=~>g>{W?#G;dY%Sy$1TQGav z70dzn&MrTNS(gx>>J#m>kx3p4eJS=YeoxXh}jFFMJJEC(dI*xH}MdOnEcaV{a#3(`~nxRM2bf4bz=yCaCW8_+y z*&5COTWO~uOr4`UaUGNndA!e!bn}xxCoH(vBT>7Beh&>>*`4%zyNm-3n!^`3x1&L< zs$IdOZJcbf_(IoKt_{i7C1=~y)AT)(iG(rBuCvOXgDsOaM;B1Ga9(VO8;+VM%z%8c zcETM?Q^dUSXQMQEP%tD8`ZM5;kIqgzcYGwy@!adhM;?{-bZSDej@t>3zP?Iz%i4aA zW=@m+BzdQ+N(obOmmVT1(5D`CZh@i}oc8Pt?NRY`T%bjd9t~lm0(~)XgO!uFN7I`! zNpM9~$3+kr>HZc_H-+Qkgc#wr2{&zH^e)|_Nz4k_Yzp%d=5B?+?bBF!fYCRQ_V8Q! zoo0TTd-yBO{h7%;#F(L2B!r>7V>srT^vK0XBxlMgWHK|5tR u^oK*Be;5LNk3PaPV>EvX{xRkY4hPtu;PV+3=}XKfAyuZ&=_`yU+}b~DK<)Ve literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..16e69b30290e402b7de64749fab9e6a44a40d2e1 GIT binary patch literal 7513 zcmeHMTW{P%6h2PV?uGP1(jsUJOiO`;iw))8l$17?CS{XUO-KbiIre1NiS2Riag>1g z7yK6_D#3ey6ylrl?#7!swIk9n_&&ziXV7SCk8DN8Uf7;A z==8J*6KRKX&Gu7Xr-nfzeeNDl3>ucZ5jW`7-0o@LP7-Wxta$CvmT8RjU-z%yY#omV zCa2U*5)tq@kNd7cmmZ{$J>U!BwwHM8KEEA?LTcqfoO7)e9(%U${leWPJK)%Sa$Cu6 z(3#e~M1=Qa0XxWDER3|YH8fv!h&GhkWYAcfcP)@t@%izIoErK{zMP*-qbTM{^3aY$ zm_Vs)-S6P%w|8_c3F@nl8J2|$DYfXh?y5euqI*|7E4m?mr?J)W<$r}QAklquL_u+ zeOQw&(%U1%=!!w7ZhE07rwqC*uF_8yg7c9_sz}^z1QWU7bEK=*qLBAHfgsf*31%s5p`g4C_5_ zHRycKKFeL-0{~sr(>s>N4-GEXk_f}p_px|ld}6Im*NwAJo_!OILn=lT+%N@5rb<(0 zmZl24$z%(Xc?t7E*Nc(4{9WMI29J5@0O>Yj5n%i%#hS}ZN97^5;rTpiHjd{MCBja@ zy%GI=P|aaNL6!hKXfb3@D~AOMt&c<#}uWv<`}Q)x1=rPI-R z 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class new file mode 100644 index 0000000000000000000000000000000000000000..4d4595dfc7d8d83630ffae54f9f587bade661d7c GIT binary patch literal 9695 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 N7jzyy!N?3f{tkp_A3^{C literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class new file mode 100644 index 0000000000000000000000000000000000000000..78d19083703889184cecab32b502b494a08c7ee5 GIT binary patch literal 5654 zcmeHLTW{Mo6h2B*J9g8x^_F&*Zez1$ZMX^wM5}D*UN~oo&N|=b0yNw1+WK2g-X`f4KxsuN$w_POga4k1hrA%*f zCXyyh2XMQfDX8p6PkKizwoPaR@blzZ?lXbZX-}{K8p|9dS_xooVQ0vH-)b)VON$2q zOs}CjO}G)jt>N zPQkELki=9$WL#COGk;9=@Db8Q!_$(n|ET}Q$kPBws zC6|ODV<$jH<73)qWF9pS(GgG}bc7HcA$;QqVc`g2=?L>AQOpphb4qPIAQnx-PBK)q zN)ER?bY2z~2JsT~B32(g2sW7WD*`j9P zij?Pj-!qF2mv3%ao2{d8 zXJ?ig886kes>u5Ydd$#TY=dGQKA$D4!h3AgJA=4{T_diSt;_JNq2$5Lo6gDq=k3+5|XWp1rF6;bEq|TnKmKt`Yl^{5LK$kE}H&Nw54rCe61b$uTK;Y=8&E zr=nB^bMCmB(Cp%JQbl5oui5y6B;Amf*ZOrmC9mlBu*RizvZ|ODv&m7K9J{sR)A8+T zzuwmMh#y&9rrKwkQ^9{>$sSGX^1~Pz&@ zM$R=lDpAyiHr$$pCd^=yWd{#vdwJSpx?6T=Iy)++;((%;VEs5-oVEjQ0(h}A`R-6* zj->$Zj`GF+%^C0bs+cZ!3*OzO6=dJuSGkVaCN_5hxOahzxD*yZYoGVAvzBW_{k>ei z|ESW*Do@EvD?63FUcLeamWHxl0rjEdp=IW_rHdYp-@)i(oN21#!D#@q+fp)JBsON6 z@MQqo6Z4J$ZjOXx0G-$Kp=?cf5WtO!p_}l{NL#ZEN|bcB4B0L^6?*Z^9)4?VDP!RQ z1aJ-i*Rkgs;15FruPvNy;`$Vh?cbsOqBHv|eDo*IuEA}*HW1_Rcknt}+_#|vALHzk zk(D`|HQ+8@r{ErZT8y8;=QsjMzwz9U>r`nUZr%j~3CIstfq=n(13l+~?)S}wJQyP5%K3JKB%uAs`vz;*Rj3X~tZ$bA5BGj!j literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class new file mode 100644 index 0000000000000000000000000000000000000000..d3849b8197c8bc75010aba30e81876467a97e8bb GIT binary patch literal 22714 zcmeHP-E!N;6<#>DDam%6I8Nd^X@jzEETt*gI8J{g{}cj25*7&107%t#Ga86oNw7eG z0YJxYC)a(2UiK|I)6TT_eT+Uxr{^q1k+NtLT5#obrWX>i#QDyCIA{0lo&|UR^`F1} zgAk&TCk1jwB6<>c$|+6K@>B1bSMEf;a^m&Nogf-?%Sm^?-1Sng7j~E+{CFvl4lSX$ib=w`^@crJYnFi%q4bZ8`d5O(K#mkxOb6CaD*u zZ7&$mw}8u4iM*|uuI}oZv!z-s)5UL}NaO-SC%!rdUv;6XgPJ9B&8W~FZxE#QH0@W$ zch)3wuJ7&AFC=os7*lf6*bjHt7RVLp(t|(B#cokvmEkj{3Ft_QtMU^pZf*5w>fzJE zH&7nLK3o*{ppImK&XW)6yg z7EaN&S}s3DM2n`luGuw*n<7ICr)Vgx925~Po}y(o`K@RGS~x|^&@22D5iOpgspPPt z1!(aUUAK|LihveQ(OK8p{1g!_nxd+xZi|~DLkp(}6A_i4BBF&;tXoa~xTqppI7O?` z;GgNJh!#)Lbn5($wg4@fV#QWe{?N_P!YQ^IIVNKjM2n`VDNc?a&CtRr+IiZdhG?KD ze+@aq)>cTLr*lYG?%zmg014dPaTfCFjbz{Nr_nwQ@7`p!-o$FZJ4m)}Es*!$NM2P{ z8oI*Wl^I&JhpW2dg7RQ?mgrnqpls6@H2gEu5lmDE!liI--SBY?(Fw1iFss)1b&djX;XRrxChi zDEuqP3@zG<82s)#C5b3=I@dH?JCU#%f1-)t3w8+8fEM45gv)ui`MY zcn`N~{If84MNkty3p13*kN7hJh8Au`t={0Dg&BwzPtkSqPz1DaiZHz9ponPk6itgi zBQSJ83#VwA8vn~6c%!BXe;L$JYB^?M3@zG<5V#$FM>Di=ikh9n(G5fkr)b$V{?Oe( zv}lT^WxM=AhoMDNv{X&u|G@@B3#VvQHu*CG3(>+Ux~|UO7A-`Jrr1<$oxd$Iv~Y@= zt@CH6O+*W)sH-Y}TWlg)I7M4G`4fXCqJiS+=2fiLBk!wjFOZMkNM7}@rB(PdXNDH- zVW>pokJ$_@o}#Yu2gHXhK#QgbZ!uK<-xx5oc#3Y0x8t?~XyFvKT8S~Uq zV;EEv;j7zFiNpWfD~1+sMXRAb;$Bg-5iOdcqnIxLOLB%5PqD4>zawxIKntg+wKe{U zqJwDR6!nJ1zp&^aS~$g~mgDDP4x+_VG<5z2c&7S~x|`F!%@OO+*W)Sl4sp_?w6pPEj`+Iocwkg;TVQ$NYKKCZd6& z{Ppmc9p~@(uy6XFMBZ9nX-nj+3j2Qxobke^ELs;vTZ)>GL}WZ16k z$M7Tn;04^;p)n0RP{U3f^`QLC0Pc0=QYY||MBecOnw(}WT?!Q=fxg$-hrQf#5uUsR zo6~_1Y)&u1=Je9x=JbQT*u%}~MY$W%M8;?Cd1+@)PQf(vc^@8hQXTAkFX7Jj5;It4 z_WP8=`Ugbe5S;Aj;e}Nc~`zPBDVdORQTB zJL2&!cEtI};3V=VumXxc9Y8FZWrfUw*rs>9B&G55-b~wm4>~{;?FMw#(=(o#B_YaW zQ8+)bAnI*L`z0j^lz2B`CuGEfP8!9tXdD372Y%3{S*0s*v_(e6+BX9)NR+T!_mVvv zq%z`aG=&C~QP_<5p4yFuCwfe+W)nuQM%3BIvq`21$Y~^dY`|Pv)O-EF??4wy25`0s z1obdU56nLtLYG%?v>b$8y5om9LV{!98c!aXlrG5)IPC-PfRyZoIOq%&PpDj4oPK7> z434jS(#Z$;J_@ZAEX$w~4V9Cd0U7_ev1&1CCg_ne_ALuHxuJkMX(gCq)@ zaRfafeUU-i9BO=Z^qOoR8O&r3g200r@eJCph<_)oEJM^fp~_70|AabU6C_WlDT4zt zh-4X2CW&ElRJvY&`fxHS31j>W$G5vZElmBH?RvJROuavYHC{PRPCi(s%I2qOe$}Zg zb$or;pKa^e5*u_@q;hENdx8H4+BLo2toU$(2$(VASQv7uJ`Es$KbDQ3*wvXFgzEtl z(+>IgiEW3+jWJ?(hFb3}4?kCiD*_3U!~D7DCkY&$He6yDiYzT=A~Ni8Sss2U>9BJL zGKBFeoF5mab#@?~O}!bWT_Qzt^CJ0#EKB75<>RNq;WEr+114_fptnt97YX3;aZ$$$ z+8(qXC?9-zHr?|RiEJ2iJ3J2$tV?J*=);+I%f@KxY7Zb-^{5{1L~BUzqr)$r`H2sF z6kOP087R=XXpE;?pzp@Z!c8xRrVnBC2x`$VqcDVV6OP5ZEs+PWUunXHrg1H=Z_5%{ z!sQi+OE9cSl;;5~<9tW%g7L>@n7_!*hWl!d=#edy5jw}c zZtdRMy(!Q8zxpf-M3G2yc7uD_zcU-R6Aj`Ht-`vfL_VAnU2*h>lPwQ>Gi(0PP4zIO zF&i0aQXn;n*mDabhb=@R3!}zTAP*$6IsYnHK*!@e4-U9wy9MG(aG0iR#MUjfdGCAZ*Wr1}h84^@9T zMfLrus=p#KJp4%YFDJB(iT-Mewymk!e#5CPoT_b=Q`?`WYFp#f_LnKz7RViP7wmQq z>~)6RXCC;BHNBAsemlix|7M!dOXtWsL#l_CuRvLYzw4l-3Vy9agVo`413nG-G|w-R ZO}M{A&Jv3}Bo1kj-yM{zY}F5S?`#TiUvD;wI@wE<_H6ec8SA6bAtk+W`!y4PdJV+8ebyl4x_uEkBgF ze=I-(B=`KNpu?pS%gMS@-J%5wA683hALk7@GmqrE_pjanz#iONg{1@ztSNGb*5${1 z!t+8`x#e|UNbSqqmM3}19j|nWGS{J*k5SfW@xc%0r=qdz8Sn&8?#ZmUqbNDs&TAKf~@BqfH0CNIL9iWzcFl!IF_z zwU9gzu-fQ>#L7NkX}3Q{=1yy5!9W(svPi}Xu9S9cfb4^jjO{xnEED9ZA%Em4eHx-c zRS?6_=!*KkdHPnetdQJVHWU)=zsXV}5eElqUYzg|vimgijN(BKQ9Lq=NA@uuSrd;e z#^XLKHCh(tTya;7nWLq_7d1_E)*J;p3&GBs1zXs7Tmwdx0PcI*@V;1L+h7zd%ECqmpklVq#VC$O0~I&XTu?d{lXkMBaGp^5EybJ z?e1tPNp{0t_4zI|n{_)}$}o{Ru)j#!UY>g|u<%rjv~5pBF^}>Cy4}`O@4T63Vj-`? z{xE0uQGZ%MX$l?qat*G-jRfv&_JXavId4Sj?Qq6e?~#i9D|rl?#6nB;x8yN_^iX?K;8VKA zB*6aU!q(gdfC1gi=w=A8M9+0tq5dkhexQF7dZ(|U^UFs164u@}n05LM7@+4S{jNn6 o+=3120enSYm*6(FQhMK^_g#8ExA=97#k(yQ6Zn?KFTrFbt?wOv}KmY#m7ZJ_V-4YF1bT5wF zn&PplZL?igb7fGAS-a-?GVyA$w^Q?&VuAFyUz7T^*5ZDfN40ex?FyH#G1c0@?3>Hn z6Kp0?fw0=g zz({>{4xldx?t5A>V*)+NqOm4dDc)H?FO5%R6z)QLE!&y2+YyiB2P_H!xCU&r?#rMd zcWSqJ$Rnn>UTk<4Rxi<2i!SzzG#^DWD${Eg&D{^dMf$tkv*9&u;Tr|*b|j&3aLcY% zwCJ|WwDNX?+g)-cZDwopRjYJ`-WVa9u3PlNln8~Iw&?PBR@&4nCN?2PU4(ok2^u`w zU|2)TEnVhsGQf#;r*l}f1Yq7c2|L5efPKSw-dG>h2finc_MVW(mFZoJuIF08qAEl- z(W8B{8fO}1B~ur%=u!sr)m?LK3*cPEDBJ36ZKAdNZM?7@lZCjBH(0-U;%(ER= zN4>ozd>%i|RXmP`wE%7A?yx4eZy*XB?41w@d#8iF)9LP=jtN{R-8*mCp5(Euqat9+ zZP^OY@Vzz^&9cKb<>-ysF@P0jo?;+^wv1E{>j5D55o-2Lp;}4fl(KKLIz_4DN-}CX z2s9i_+j%Zx99GEVBukX=3-Nn)gnLeJlgtxUgSnmHxH~pN9l%>GRy^7Za*;Z*XqU3- z^E{o*>L);ny|>8HPn|5yt(-?>Lwy@ehjjj7!u)t9^cGp%(rMU0y&mBFTbAyQ&c6o` z*u6BXlAiL%LA0n;dkzZ>l$W$AUUJ>#)9VkSoolZ0=2L7H!O z<}5H*$!I@kYIjx+T_F%ml9|V-?{bktoV{-3koGPjIfR8Y>AwQya6AbqzmrMm*@1{n z3UsJjFvVw&4?L*TKm2v@N&HY<(-T4~w7!JY63*pq=idYGpRPTZs_4ahhDSsF?&j zR7i*+&!LIaMXqF((=futwDQay^!W&*y2w3y3P;*T#KKr8vCE%ju=U0J6UN)oxu+p# z>O7SqC)fXV1H5yOUId`tnlfqEL>51$oS(Ks`EgTl=VmFNG*ho9N+jiEdl8a6FZK zi_T?0D$yMj;`@OsQN6DUZiFb&O5bOWH25LsvWR08)^(K_s+L!fy)DXE(RG6H@ag3Y zjnY|q0j&~RLsUk)O6SlXG41o{yI{sIqOIx1@L$3IWqKK72feSL_fYsGw zhtX@l)9ZhuHAFY?t7nt-3%!Z=k(7#V(pzX*^ftYNndzG2Ib0_SbA7LXjK#UV57Y*0 zJOmw5jf->ppex%2XzqYR4bNa4i*vQ+RBPi^BWxM~@-e4pVd zS(x%UUJi`jgW!v!?5$r1upg$cy6m-U6kt6Fhe50@xMmBgR?-3h?7;*9;PMw9#&qaHMECl;XhGHAnX7D literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..84e5573f8f5243f9abcf1e806bd1ac44aa939601 GIT binary patch literal 8992 zcmeHNTXP#V6h51VIyRw&lyYggY)gTJiwz9dHUvU0O~6Uo#-va#Mc$QdCF_;h)n*!a z;a~7e7-nDw-uY1spR^m>>ukD)vFUWC4~kdP(b2i<=qP{v{o5Zzv_kCyO;}V51E->R zs4BZ`pH&>$u7s>paXcBhmC)U*xJ?Jg zI)5Vqziqzm?~72>J-+DsQZdbV2w_wqL%}@p19vxBn`7}q`iPE|h*rGG7ukhD6$ z#B_CQ8Ny!`+;jCNMiqJy(5Qg0K*LkIo3)L$Y6>tS#;rZKj5MC_PJ}r z*E<4U3v;wPfrNdwFWaROU7(ky>1CRQA|0UyWSJymsM;4}S$QKtj7g*RE?BSPxr^7T5x<`SEv$4vx@w)N%$mky24W#RBrj(48YSB5P(>oO-dt;}Soum zMj+ehe;;Aawabn^KkbI_c=&(r;&N=P3Ft6qk2Se{5z(oPOaufW6DcDTDR(oGvN`_B zaVB!nb|nvOZOm;3KerV`!|!y!Xg@j}_+_2ul#RfKMjp#Rv^z3TJy~Z6IR&WEH-&0N z^#_ot81q0%i7CmTS%#w_z1z(*5o54IjuI?V!W#o{op!FI#^&u(Ha(ulli7U?kchp*Z2iB3#c5CZm?S0@U=q-k?<3}gi@v+Y!j?{t zGx@rS^KU~sd-^t;0buvCqhfrVj{;~-sm?Ms6u7GDH2khv*$DSNMtc2B4kNXGczt7C zbXf;Jlf#C&%I>)BI2BITtTIQ*;2>vdcU2DDP#QA1Y#yT26+H*I|I(fV+Plu@01m}T zx{#2AaU4{BA|v0m+afe6*k!w$o(`0W%BkF5C#5=XV>ap@!nMPw4p&f-;&wv@CXuUa z#3qjmQ0>sRH6Q1>HRO1T`)+8%CgP#PnS}*gHq2^$nXv~+&<-A(o-QbK7nO=j@s&vU z$c^LVngz|M&3%Q081xJ*TqqSE_y{hG#d~gj_lJ+@QbUew4(&#RSIv9lNhxRkm(Fx_ zE&S=hu>^M-=2*Iq1+~Fl!Q%Lv;|)W(^)S-9KLS+u9Lo_)@9fF}95q7JgHa|zDm_pS zP72g>w=B9e_~LPmCFk~H|I^3$j5-*q+PY$lD(0saUB7z?Ojp4E<=Klm9Fl9Z2%oLw zCSVb&ZtE76wq+DJd=+IcRMrPG27Lp!$f&H+TN4f*Br$6Ob>hHj;RRxP-AB>b zxDF2sgcqCD(L9KOgcM5!`r4u|MuR*3#Hf!Oy!@qh(_;m?ZPCpU?KpVM;j7DGR4pi5Z&956kPW~050c?-X#-|51aGcWu~FaC+%1kK^E-jysx zui$$+hN4&LHS{Lw^$}qHF$1$jWvX;Jd}Bx&f6B!4CcOnrhcbE_blYfYj^D}P_?t{@ z@6r2RS$>d%HA{}!ST7GjN6JpS7Q~lX?xUOsA<$Tc@?c- zVaXakY&314JrS%QMQ?qZfqs%c>!R0=I|S-1Ib@->s7f1&9Qr9gB(^7uVKEkH6O2yK zRuY@8b)|A0<2TTD(1>oLor5@V_|PpX(pUKX2EX6p_dERVVO~c`MhUhMa_inMeawJ7bbXegJri9JcCv1hz9j6(^XK2Me+Pg=__6|P4*aN9;2Wlme?bS- z4`l3Xn)pE^)6myp-w&ywu?$({OZ(UFu_$56KVxdZ1NM@dUJJROe#ateSa>K5H^U|u z{R*r*@E{W!QPK5Zv@cj-9N5_Bf}6Sn+g~*&Z_kY4qPzF?xdZD5sI3Au2kuYaA1WnP z72bBXUVkqX(=r;ckf5~*N4;DMk|^1ZSgd!6TWj?gF~furE5Nhry<9yBs1VYSHX~O; zJaTLZ*My6ZE3_~g;;Rl*Oa!Q*qhyThS1G;=i5EmvYtrEn)3>Q5WT@C0kkFt{yNvAM z&OMA18iaB3Fize$PF@x#FOSm>2_@6S?m4C==n;diVV4PRbXpuHfoGGz%N$l(csmr*~Q%kFMDZ3FX+Eb=|36$7-%c#C}vPi~l z+4r8~q%N{=M0)rtrICIn!Xv7CcJ$uJ_XEzq6B+dFtiOSP7)fY0a@|@!$`?U&WK8lL z6>{;WL|=~HH+XMaSsAZQ(|@a_coxzGYo@?6FRb?SFwT+Gh^IjVBh|2gb<9;jS1LjW z8Pu8YEz`R~5Xfo7e_`P%jc-bvX=3Ywmr5ddG({W9e9+)5LWnm9*rsKc+R2?;`Hf6P zNMf!t2)0Yc@bnsp_GUn|1*I0ZQ8GXuheW4sECrJqMmkbuK(1{CXE;uw|7~vV=Qy{8 z7|$>fYJxeDKe~ETuM^qHxHE)&-2_|U6A(XUa%%^9Ah?vJoEk%Xqh7VYt>~~XaGxcsin*T+;&GW8G&UUnNCHEih zO(!hmbdguB`yUxQruEft{HH-yEBxjVD}d zJlt(zVqq#*PZ41rNp0YBaNr&uMFZX)TNQY%;HrU7*YNKC2GyUo++Shy4_w`Yckyaz zEd71FZf5^ocmNM^wKenPJzQCfoL&a^Y__x`9(pOYM0PLxQ@I8bKzAX|6Z+uMC^cW-a+pMU@Oi-^k9%+ZiRcYMz( zDekM%6Sl!hmTZ-L)-G9&4D6C`ua|75SWDX6DM|fWYI3K|z0wNzHiX4*Gu5nO^acG> zc9l?@IT|+TSjf#`ZlknZd%`V+E*^fKk&c75kgh>vl^)rO@`T&CX3*go>H3PfYLz(w zAECTKho&DzyjM6F4Xe#IH|Th5@QlNJA7k??qTw}xnR9>nsR38|$ zHmJ#Z22&C4I%fNAe>e|p)%(%W3xBY|)h)hhP;PvpvdG%#`ZRi*jM-ktuwy-uM})SM zuPjN|4IBq!PsT|$xaSF5o5=7Cye~)R3_9BLma^wbZ-hQD=-fTmUU%1JMqq--G(LjYs_;INp(0Jt2nY%lk24$ z+~uB*8P`VWOM_-pZ6>dSk7+R-zRVgoyC$Zj$xI!93ov_#S`B0Ii9YeRqH%L)@4&pV z7d}EiI@Co?hv8#rZCkQKSj7ldO)#QlgrPxaLI|IgLRCX!EA?FRjq!=q5t=r}e%%`} zA8wYfHhGAQsTZoK_r7A}=?OR%%>`u&-xRJbJZy)~CLpeJkGmH9xbDdo+D`*~v(2KV zcQ>;x9PYo!S!|ApwE%5qt+NI<&m+DRksUxFWCulL2gPo7Pz*PyVw4@6H*Lv%Q`=69 z!C6fOX!x@>6pgck!xk0thGGaS3J#avz~)-w>)9)Lv8ex%6w4I;JGO zMiGvN1nfzgiG&6#CHI`b9xp0!eu+Ba{P zvf=PFox<7offBJd%hWHNEXp#9yW|Xg0j5J*ei|^xpLXqe<~MbMo2b_s_P<4Gt?SJ` zfxzq~Bl&1M9|Y07QteqxC~%O}iS(XH&;aMn0!dtBePEonSsM`Q}n#;KDsW}@sMY|{^FuyYkCZq}t2W@$CeI84$!6dSZ{O-4y<6$zZ; zuI-z!iKuJq%hZ%9>)~j9Ffg}^&`Z_zNxwU&ke`W;7s5s28)eSSYXmLsDrCEmXWPK3 zLWWaG{M_?BN^I~b&b}OH>+|>L^tYX3U%Omb>d0r= zxpv8Z;#>CrH8w4BTd)WJ}f)-_)g4R7G4Xzvo3u7EwKeWMNsYYPYAwb?ld7=)_Z zfbo)t(ajNzDRwy-r72i|MdACXn(J~l)^KDA2f%shD>_GDj{DLBG=}FOS~;|aXawy7 z9YT9FY#&D7k?{Lbv^Cv{u>TajiT8NlX?ly^?v6W+QNwhG&SI8`(>oz`0sP(thxhQj zkLL=$8~88$N#=vG^S{%DztI|^as2Ap$SBhU-bW)U`iMS8YnVP60Ov{qXIynr5p!je z?M@btfW2%A9rUrV3Xzf4vUy@M( literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class new file mode 100644 index 0000000000000000000000000000000000000000..07c10b7c3dc1a4888a7e5dd7cba4bc6757acb973 GIT binary patch literal 5912 zcmeHLTW{Mo6h2C`*w(UMvZmX*8<(}f_9DF}fq^cKkqk~+EN+TD(h_YmmPnPPjCg-+ zKnHB^`z`wo!wx0KwvrUm0K374JeV@2b0N?5BmeyS?e74v4^L~b;=n7d0^cxg{4+YD zejsCC)5H%VnTEa&hki&6jb+FpU)sO^fJF&Y{s~hf9D z^+P$SLgyOB0CZQx%-@`;>%AA#jNEWbME|M%YJgOw`<_OQdtd8ZC z?`lme1d=u8S&B8cY)_R5Z36eGHcXwzrIuP+woBQMSlOO3?MtA<+FM2Sdnb!z+?7M` zIkxGt^-W0k-=s9s&qR1Y^}x2?Yx!Pd|2vYw(DwLi2#Aq{W-Zrki*SBL;PjrcDtJf`t=fiq8RP4H4lM2@CxBbg0aoJ9!nt^l@qnU!{OUo->srAXj#FrV8(X_M z&g~$^GfaejU@%?%&1n@qMszS~3dzsR?%ytgT5D_XUb`w!Qq zBNlQxiP{Bp$Nx`(nzp)7_ z(CCyRq{8{+<*FHQ?ZE5yQts$>9C%dJiU~B)++B{!-OdD|>@-okdm>X6uohmZIq>i@ z7O`i02O1~5j|YHMA<17C1#-JRb*ufbN~k~hcWxWFT>HD2g; zFrF}c>&JMCJCNGIH{ie>yn6@KM0Qe`v3p{ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..7c98fd44d8dadc9f178ba0c5f3b70278331a10ec GIT binary patch literal 7167 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..8127755b698ffe8ddc000d2093e8ee862c06f742 GIT binary patch literal 8162 zcmeHMUsD`K5bq_x!39YmiGPxmP7w9bxT{8A#Ig#J5G+Dcf>@U2!>~KYP1xPJ%*;~6 z59C*{N~`qUui$sF?Ag7OJvg#`WX(e=53n~gJ-_}lJ<~lsfBo~@9{{ihpEY32gCC6! z11pRT9`QpSgenOP?*?J4vM4aoK@f4v6BUU#Q0`~Y5ph@OUHe|@H@?kMD-*Gi9!ewk zVzH8@%5p1JYCO1Eo|1LDS{U;^*D205ICk(J$Yt^inM(Vm2NzmPyB>_MqWu~$@4=;GW!JP;dJ3+3b9cT=Q3VwriikOh zWSnctx>}*?iTRv0r*}Gn*(Y7W9Dwhv=@-^yA$PW6dxG^re70ekG)$(E)M)Vd2@bV| z7HNna+FB*pew^W0#QZSk#;~@Gg?V0FoC-%nL;@9XigbB+!1o1P zKnwcl!KfH|u#X<>|L4Je?!kWH!3!)>!Z2sMgxj#g&@q@e>Y~BQaOe`g+a7*ycrtil zOPO?)w#AxdR>nh=K1F!s=L=f6!0E+(CVD4+n;R?iQBo<4Bi>hPKNeMDk4A}$Qw3OT zTz@5wSMtO;=FZX(db0tEp+dZ-QrR-BH< zSK11~ZJ{NP<Sy`tbwjp?9T-@ydAW0$5g=v{_#&LrAjfUKzqpIP=<2 zUg6dJ2LE&~*ivbV=|L^Nk{4+zkV+9&V^mD`gkJ4n(LzMI&$bG$yv+uK>tGCIZ49gK zQl8g61}U3On1femU>08U;99F(5Gra&uIzGY6GQ1%=9ure#K;w!RLJ99j$0Soz0Psl zkyuswsh;Z3@JblggdPu*@_*(zyIDSYP>AcfrH+E*5<}2)4IErNS#^8JHJ)vFtge*H zZ_UmAPFS9T1#j|iLivi|>!gENg1J3+q@&_*C7 zFs^HXC?=!O)G1HJvXEI-xdI8Oexpb;b|!2ftz?BgNHH z=Yw9=1iPEtD$}93i?2@}Tp3L9@&^|WW;RlMy~}f4VHyy4uvW_}9j1+g{CfV-fKLYA zEDIG-j{DLWd|}+dM>xzDr?&7-=$YH}fyRTlj5wNXqa>AAZaY=J2r^CMs@AXVwHcsCj z$L6LZwqU7;IE#Y6j~4Bl{QRgB@B!kC!R-O3_|UBuuAm1#d<4^Q2ixP=`WRisQTzV_ DWF>P7 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class new file mode 100644 index 0000000000000000000000000000000000000000..da008aa341580d823b8f9c66f3fe3491c7a60dda GIT binary patch literal 8496 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..011bfb2a69ec16b2805c672ad605db15c09d9b73 GIT binary patch literal 5131 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 YRrmzIeSzZ=`tvD#1~vE+M>A0U7ZxchBLDyZ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class new file mode 100644 index 0000000000000000000000000000000000000000..ebfd189c066a70a21a10798ecc558297c8407c4a GIT binary patch literal 12915 zcmeHNTXWk)6h7;wiQ}eCNn6^cT$WOb(-xURfzm6k<4n!uHjNX82PUIPn^>tXMp|c@ zXMO?yfEj)SGcW`1Jo0CFV>qi-94V1wIa#Nj)(>{P(dyfC&VJ|Yp0oS+f4~0;09BYC zfgFM9z&A>q1-!JWZ|Nn&b4r2UEE%@fGE0H^ykzQJcRZ8XB~Sb;HJIIGerd&YsxG(q zb{*dxfgu8CLjYTMH%bd@o6O(@F4wC|_0{R>OnG^>zPeO>wo4_P#*i%Is6lo3UPtzK2?Iiv=jf@B<5R9uYWEoLV6;R6+Ge;2eSR zU6HH4@A-MSKrY=~c6}Cj_7*cKny+cu;kdNvduujxf_tz2)e;~d3g zIIs|)sTHkF#7sIeZd%U%a(VWgLc-x4#v7hVHyD?Z zvf|k`(i~$FTG(5c0ii9N2wR%?lAWm|jO*=)gj0a5#&E)1j#dk&E(|DgkizJ^=ejN1 z?jn0e6)Ry>+3y4KDNAZt$Kcintuo4ch$)$t%gGY+w=9D#>b%hh@iWQwgrS46a@V;# zEd3)*_q?7$l#FgDIGXZy2X5$`>qevQMTRa4lHQ_wgxOtvQ$mkeAVR8la+ShpN4V^A zqL;?FFyr|%3|FVLP?$lUq;IIh&BUkV;ITc^9m9f4Dzxx1O{t+r$he%#H)G{}69ERuEt@L2el&rcB+U?f6R=QYNAGhR%x# zbuw>uk_s@s!u$Z!h?P!!oUf>gLcv$Z_yw4Ni(@bjm+(-dxc@jroIQ-s zVsUl8<*YHkE)p(rZsHlby%Hs0(LaZH!wLw@%&Iue!4r^^XsP8Uo|8Ol;SR%LHFwJj zEF8ZqPAkF^ISzMfwq3irpN9_!{FC7*r2R_AcoCOO%e5A`+NN zp^*$m$SFMbT(>sxo3j@@4@HvuK@FD%6}dr0Zs%Y*R-q?wts@;{nNc2Y67sJaCdT&N zd!w3k%t3+gz+|9Z_7E#b?4U4_7pM(c*Hbux=TtIkE$Y6Gs}xou@}QAirdtr**Sa3RsiKmyThdlu z=ur-+T;JFl*xvWaRqCLct%%I+FrA3YsIh8D1v>YTD)rDc7TOfZ7(;snX+vOA1h1&1 zhj(ydkz^eRhWbq^cH^yRZB*bQH8+87~_(3`-({i+a6}e+Al&P?jAunu2ySM9X1cfU60N5Ai?yCAyY^aXUpK zfa@91K2AYi_Gt>5k&5j}1p@yR3j1}gY_NId%=b$miV literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..353f6ca254db913bad007ec752f5f6366685c1e0 GIT binary patch literal 8660 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 V+H^n;xzr_}LOh$n*PqhSe*jEZVT1qx literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class new file mode 100644 index 0000000000000000000000000000000000000000..dc481c5d97015a80762a96f942b7126a08a39bd8 GIT binary patch literal 9320 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class new file mode 100644 index 0000000000000000000000000000000000000000..1622169903914ab5e3a7303028ebc42b79d0e30e GIT binary patch literal 5759 zcmeHLUvt|;5Z?=_9edKI34{VIaD)H}f20?lNT)NYNtz69%EW0H-Z)EV`x@y^oKBwl z6YwG6doTkt@Xm)~*pnP8$8IFIcA*0gwsfbx-M!!L?e6U#fBy0t0PMkMRahYKU93IV z@YuMISf9C`3f-7Rt{14pcVqv|^_gLz@_FDY`|9?15OM9cnelq=2`)VK!#!z)8Qir~ zn|3N2H#BF4S7DLB^^|$QWY>Mre#AXPpu*Ed1Xh|OTy_ZDJWlXi$PZ;-#G)PWT`84e zMkpB*SZdzm#&C^_pGa*bQKWesf5WsCvK!;#D}5FuoWNoO zcUOfK)Z_{Mp4Lh)!75q(@u9?pD(G{cB07->QblN_6>1j7U(kB(q{pclM4Va%j@_n{ zZIgP;s!rRSo-=Xip`jwCLi$1@62SmJb-3ozLkS(NLL7ge;5VN-Uclm*c0|DA7b(T( zDA+0xG4G6ZIo-gWJLngv5c-9Ke&PJ>7f$LIPUaU*>K7Z-S3IV6=b?29Y7ij)IKu7b znWF1C*7ckewkbtqc!)4k+GMy(ZYJP8PL~)F{O1TCJE=>N&MKVs#71du?5ZFS~k5xmG`L*nCMINa6UxPd9a=)^NhPh4#y$%GHo zD9X|hU9XjAmtU18w|z_)(j252@GgsmS0Ji{Hcv$&FT7$=Mp{_@K=L6j;`X{=SXR1>c7IZZ`ZE>3~RrqH0$_p zS6D8uulOn%n1luz^p2 c_wi@-B(Mn|qVAvIxPsq5f^GN|-xpx>FQ4tHBLDyZ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class new file mode 100644 index 0000000000000000000000000000000000000000..4a33fa43a15ddabc6d8a427c6da5b1e842551fba GIT binary patch literal 5759 zcmeHLUvt|;5Z?=_9edKI34{VIaD)H}f20?lNT)NYNtz69%EW0H-Z)EV`x@y^oKBwl zl@~q)AA}j0fp!=B_&Id&ttwM!XzuqB=LcK3ewyWQ3P@#inU0l*%7R)qxu-^JQ< z4Udici1nH4snCsChDxU|gvbS!J2NBn9n;Ea?p5Vk&Kirc>n895; zv}uR3=P!A{4X?r?f$ORAfXS}=p#6w@hCqdP z!;DZeCa~1J$Bp3{1uM6O6y^?rwXF+EwoeJ%YCf^rN}@>fIR1udDP%Xs$yfR;NH~GT z2Cl9OE9l7+`8}G!`qM*+6IIaXK1FmQ5u}dLNGtR#jK84u+DVU7Gl)2~3LLvk zCtD`UaT*W7-h`k6)w} zpQB;xK*YQ=*5z~qSMDG$&>`f7gS>G5=7p2;!pV5yq`cUmzTz>pD-SIxs6l}E;|Q0V zSBlhgEcKiewkb_yc!)4k+GMy(ZYJP8E|(Y){O1H8J1G-0?vAr^45ex0mF_wWse+qa zpm`pWL*5jb{$GsyBfI>M01~LMt z!f9laMEGB8)ffIHYWqwJhWRjKm|?n$!8lEcs)NxV^BsnNn}9Kg(=dtCl*ni&k)BQK zEWmt-IlaR?RB@!FOoB-eVzca_2VHG)66Xvx~Ly)}ctlFNT z@=}rMS-pK2Vf{EuBF)v?hUPE1w=K*M0Y9~0G=82H`@;ZRz>89cqm zsK5p;%rf(ZrztS2U4FLzr3gMJxu%H`)5sQYeJQ+4#r$$qdxn!Y&4NoU=jB6Xe&s_Q zuEDFzP=nV9tZ(JpJ!!$bW4k~rO(FSF61KTMvBe8Ei<-(~!RfGK9S$zqB9%b9Iq!ZL zcAjcj9A}^qSl!yb(7Lh?W?w$;JubP%%;WDY!P^A(FK-#@|U3cmjcw&7E}Ux3ZO0C*Rv0{{R3 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class new file mode 100644 index 0000000000000000000000000000000000000000..d219218734972d4cf68866d2385bc06f0ab80269 GIT binary patch literal 5837 zcmeHLUvt|;5Z?<)oVae&rVRyJ=rs+H@JD*#iDWvHnuKI<6DCg6;f3KWo$YI+J8?RB zYQ72|gc+EDcRmZBfniTFjhwiZT;dXjK3KXt@9pmGZ*Od=e zH9R)%6V_v{r$RSok?RF2@!i-zc70}8sC*u{$`0Kw49HW;1g7h#W(8^l7Eb85v{rf!u8?a#K9aam z1wHOlJZ2<#?)h0BiiEhZzc}iHB`h@NMC3? zRM5x2+FWz#p@gS1HBkDA+0xG4GglI92ApSJM{I{oYZV%ipcCC!boY8*_DDcYfv^}pi%bJQcN%bGmE{B1!#N7mY-0=iPFJur&P-Cb-5mngSJ!f?r;A87 z-xM9mOrlZam9rEZjUmrjzi+oR2K6eAZ{%3+_qf(rzOT>0y9DmOxS@spECSW0O0>tf zvGGLU=8&YcpiVnB1pa8`J>C{K8+_To?Ny(eqcWGC8`2C4--hKrNu9^0_ zeFC*zDY;I8<8cL62<)G;7opc!5Hh#P8l)d$gY>lZE3ii3@(^4V_;{$XIqD zzw7v{WmIqjmT)!=Z^BL7`4*0+;BBbjyNaWC@O>*CpXPjf1m_<{aGr(tQl57PyeZtp hIlwahs;C2jd+zwwdvI?g=hD^}{`Bgc;nm zQ=4`w8$XP20k6U$f$J&rfXS}=p#6w@hCqdP z!;DZeCa~1J$Bp3{2`jgS6y^?rwXF+GwoeJ%YCe(LN}@>fIR1udDP%Xs#aH?)NH~GT z2JWs3E2zm6`aP|cUV>G!`qM*+3sunPK1FmQ5u}RHNGsGVjK84u+DVU7Gl)2~3>>>n zC)+0Vm{py&IX!3M&_hE-OojA?MkIm(e(G?|rH2waT7@|NKEZE3b-aMZG3|(e$1hTf z&rz^dAY$Gb>vFn*J9p48P$BdS2mQkN+b^8dFPzLToYXHisIPcT?ao8%6x1L<{BeZa z%`-*UbFAw*DQr`U$nX$hq_oL!m)uOidz>yYBKXe{K6X-1%zSs;E5}fphF$%e$o!*IB(yKw_kfYFI(l%BZSMv@61 zs8N)qA-Y~G&n~|zO>X;`Fr+z1GvHko3$H*_32mN=L|%ByXiQoK!gh@cg?A_o@A66D zjTC2YBFJaqfS}@FqnTHvsyBLhql&Crw{W0HR__qldb2tr?I8BL+b`t5? zw9W#|hnUkl%tIDOO3Ead6r=NdC`Xmrtiwd+Vb%vf=wdEb}Vl7!9IB%I_&ajFcs|oYmr*V)x>o3tf zW|pQY6`7t@+w5*ILn!8|Z9{Xs$-Kg8BX3b8!ok-mk*Ws zl@E2e2Cptd4PGO#zLjtHqy_Vi?E&hya-F)18Tyl+>$KP3kw+ZZD-iorFs4{7gK)t0B?eTr= zD-pOc;(}?toHk@o&z9&)W)h85KQ!~sou%*F^9Sn!Hg^!c^+W05EfAxvy^9G1NPE2j zf#pMqXfw~|ZJ%fN#Z?e5VzXTNjK?$P(#-#`6AL`!tTqzQxi zk#rr!Bjwy-J?6M!$B9_ias4p%oXETDcucWQ=yBf(^`q0~ewRyUiz&D5tmDN^ud@^= zq59YK)OkI%90#rvVNmaML#b+^@AG85#-rF*D@t{5aAne@K}Qm+J_}mTt*twdGH9yD z{Fp}u9cbKS-Fbs%nxYjj6-#c=1wFUWc<@$JNfET>b2F))`Kx&I9wMpf6!(Cu z5qJ;F#Y*`9Il+5&l8I^k4rk>MO5@0fvSl-*3W8jqc|RnxyeTsMzZmxhqWnw%@x#tm zc-O{-tGFx*Sth)e9fymu71eOFDH*#e9R0RFNk*u(RA{l}h_1|(SC@Y)X(yJ^3Iu8; z={c^kNVo;2N_g`~B=W}7JG8*qq6$00tp|#?MnaocIF4)_3-7iJd*~QOVV) z<4WdDuEq^uIU56gXMxxIB|EUQ{x`h8hkm3@Ym@7~S@&sssCus|1bujcSEXv`kzHH9 zc$#rHN#$>u4AAXI1%~sHS>p_y^iWTj*FH{ylw3_pC z4PBPtbLOgz;rS8Nou<_)ja9H_y3@hn{KFjEZ6;X+D4^hC z`ZU5;jwQ>{gomMN=h$9_C6IRmj9{7tP<+TuuBF|rpRP7G*6P@@rZhLC$QJa5nqe$m zz6>78pwj~>m-NR80b|hpMwzp6;VJNP>4`Hy(9SoqjVBRZ*cn|-sMVA8z{Qs#Lao2g z1eZ*@?Y=>?^&sFfVODu$(uW4!+GA1?nWgdE30MKgd>Q#v24j;xHRxb2VNLoR$vzlZ zldgcl)%k7G0vJoAn@ONwr&6Fy`WlnS2$QZ2@Vf~dCS4!kyVG}o*iC_tw4k*PIumI; zy^77PgpnePpbb<#NLxBUQ}}OUG(ppNo}?M7U^SgNNV8a}VC@k8zl`5u{4QhN(DOgj zk?)QjyH6*6#b|?2efUJU`meKSr3Dr1SL7E(GuHni&s*KSyeQkE#Ib$w2*Uq{caV zAHF`UaeOF@QHW2`1;95!A7q$cBpYKQ2fqq{Hs}(4guGe8_!MS7rcdZI`hw=^OZtiy N;aH89u{J@M{sEXiRdn^STx4yVWK=&i$uE* zd5gPV9J&c_x?T{czMJ@uT%T(m#=Z#L*xb51B4`Tb*17g}+zm|J^2584mU{QL8Jagk zD{16u8Ap|{87sZ2b@M@-Xp6=foy^n(JZiZ2>koye8BMf!kcx!SvFbhETx2v=lZ}Y$ zR0&4c&Dc`)>8zSoGHNUqMsl3RTbMbqBqOPBF}k`?5a^xK9=W!~Xns9~^`WRlEt$xA zARchVL!pIQj-ps|;|FY;s;ibFjAoZ_8tJ6^z(#NL*HNRCpAFQC{M(B$4D-VpX2Ja@n(7z8FcV3M=P zBqw8%lQYT5m^5emu}Ey=d1z>4YXnFUwz8V(0lkmLc#hauVK%^=nPiwVCg%l}G1f;g z#+E!NWo_a0_I9|e8c7-3w^pgABRt{HC*&@A~)y&&G zk=_7RLs;|NNEDUl1#W<{WgUmot3+Bf`i8a$IErkVO8>x3T-9-eLPk2~1Fb?J!NUlF04t_R)d|sE1(0HunJ0uFV7d9Zo9CTX_d1S=59c zHG#wF=sGtIu==oMyFA zM~osnIQ2utd#F8sII6wC${WSuq1<`-qH=i0hZ3EjGt)FhXBnOA(<3q+q1md8#9A8G zh1xKO7e=S6vB!h0_KCGUIBpK9jDqUXbS;Oqov)!+*rq|g5^8J=j3Mcx-NqUFT<^6kHXmP<*@mP4N7UuH_JsV6~sNTVi zL~jp!>tfOFoTxCoR9!=p&4e8fGjMjV9S2`6cm=lN&5itS=*HkIkUEuZ4edQfXS*V_ zr+>!FD)*kv7Z$3U>lN5)x|o|vwqF=aYjLVPv4U0`qYGW3%|0Qr9u=b})qZPy=_pzA zOx~+)XHCQOTqW}Gfs8_AK2xPoTzNaYjHWA5Bvi&Gky!L0qx%O;b4C|U%sNSVwpIKI zqvHj#So9fE)-RG4eF3UVUNSVe!EYyuj3>)0^p7){`>g{Lu^#64lpDdGM&?q=^zj{J5nGP~CYnZcRMK<*&UwGoZ^7HgZ|fgjr!exnckIK4~rdxl@x zQ!|?3KlMd^kFFvF9m7XMtoKD+plgLtbP!)46tWNe1ig>&jnVaX7C)dHcqVf2D1zUx`XHQ82K1n`JBF_o3uo?=vx?e8~@BP2=3pCR8a8% literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class new file mode 100644 index 0000000000000000000000000000000000000000..b91e082dbd13c048c379ae52b19e32206ef4e478 GIT binary patch literal 6684 zcmeHLZFAE`5MH?fB_M+q|=WV>0%$~#XX%oWIFvN z{UM!cXUg~fs7{}g>>$Sm$u!|*+7C{2cdKXjp547$?O%WY{wERLq1z@^4Ei~iuA_LY zoQJH-95)J_m~|Z2j}p&`y+@A66bmAc`%a`Eoi_J7Tsj*}xou|^FRpvRolpt2bxTiO z&{KDl&{ZM|>p>@yswu*j&oh}79w)w9QmXSES0+subU4-Pv#{md-+0JfWzhaE^AjE$ zw6AfGb>>AeDbs<9k}VdDEhJf72Xzssc*9>?@V z4OE(R#Gsj{JGdidBq!;ZLFd*&$>YfHa?gS*I>Jw}t&WVKEVyn}t8|!7Ow&m^1)df} zDAWyuPR!=C_4t@uhmM8_`En9$aJk9=V9?P<xFkntXJHG=P9xkL&7KPULuP9rhP{o$w_Vrd-tNVaT-P=S*RkoQ8e7dJ(!{}CKGw(+5KCfY*9slaOuIjr&l7gHhWba^Kl;{A~u=Jt5dy_ zr(nP3q*A}t+PIQ=ldEwZSWd@4QCDE~UdayZZ0mczwTpIS)ozpfxW9Fcw~Nrbl_4nL z3#=-YL(i<*^2yUoI%z2Xz+{Negt$O)-ZLwlp$Z?W3G?XV1V|qGHr%6j(>0||rWdt# zt%LgUjj^^S`D<=%_CdU^KXSc%RP;Za3xcjxa+DH!nTfc>;&!pVTIB143m4Dc($f&Q z)t;Z*ZzzNRGPuS$BgT;}c6}+nOP%@ULhS`IZ=4O6+Rp38%HEX^XvXo!+)WhMMLjwR z2A!F0+{S}IZx0ziKewafW>B@UjCPtXW6&Quh%$A%^MihsiKZP0#LAWIsdQIx21xzG zb{U-{wCe^MfSK>KN;kbswhcZt+gMwv!%*G9T$7@=0Lf}IN~Fv0qWfdesewRAtJ<_} zV$hRDQGPDG(k|Q%59}D~j_Qee=;FH$=coR<5?nIrwzmwLu7@F)X+xFACVgVi{atP+ zqW7zL?K`fpx%`dUyVk_484Qr=}d~=rPCM@ z`pNG>>U}&NtYAq)W`Fyj+Nccl%rGcqhe5$7or5`jGtUD-o2iwU#t!((HvA}?&yIkf zpbzN6HX9eW)r`l+rIE-V(nnyUZ}@nKS4Sew(p-*;KH>{dacutZI%E4 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class new file mode 100644 index 0000000000000000000000000000000000000000..8bc2e3a6b39c13d6398beaece6cb4c18f74e1433 GIT binary patch literal 6662 zcmeHLTX)+;5FVK{O5CLBl>+6WmX@ZJO586^Xi43c_~52JaS{%X8);*2tBbo@ndZ0f z6L{c}bKn3kJi!}3h{Ly%?aGZ>*(K@az=LIJcl6EdH#57V`Qz`OekG!7bk(GaK}jrK zNAXxWw^^4tZWK5%>o~3-C7u&|cN~u?7DOKRok-t0ZSHrtbT*lC+s+zZ-0*^Hp%QBQ ziXJ+xhps1~t3(vmgH9w>Q-m#_uktwY)v{8ZFS#;l!k~kxMxTW(=jP^Z?ka;OyUb5` zY|y^O4c1vOXu2s{AybLu2A$Vqi;eqlHI)SPLT)6(S-6BZ545<-?5g=J2IP_Xb2-U$ z5MuK?M;FiMkN23?21@y`g|!2BgOPW0YBYXiNY}PeMlb70o?3zDTT)| zJW&IWCLJJmA{_@+ ziy{>2l0nDjbJ}|5p4)(qh6wpe5^Qq0#sFZ@;YQ>#e}hS(pL>H7sx4stw~eO~2w>%G zf&IlRI{lynZZXLMuDHy3Vv61{4t@7%Dr0atz1HTI+U{^m`@+^?XlLPLxlB9O+T>Q> zAGTFjmWVA8dO{*n{cTKbamhm$&f1bufakjj<~+-Gb-Y}D#1^JGeFpoY?faIaC!aZs?T{F_o z^tjfpcThh*H`dl9f5xrNK8!c?C$E=}ivH(wNzjdy93u%m$wXXcal2e!E%Noog@b2r z>1ha@YLCwC=aj*J8C>J+5#z{~yM83TN80m83$@3{ym1yh(sG_XR`#xZKx>ZQ<8GqR z611`o5SR0qu;bgxWw@xUQwE@XEj zw+ZKf)ZcGc&`d(}Zm1(T&_C;@=gGk4_8bjozaB|GN2ktRA}fjKg-RKc)niOuFrDgJ$Ys$Yt71<*`Zc z8gz4)8;J&;(H!=j^oXAF2L|oW#gR!LA=ZZ7W6~!Ey?{_L=`(|@G7p%9rB7ybnDjYb zh0jg8Jk-HW7jM$iP)9p^2mIU+_Z#O25QD*yB;-7_x^K>ppMIZ4osQ9Y@ev;k>eHA+2 u3ypW^0-lL%{Hh?@Acx*V2wlPRQ3TQZ2$PTLQ@TitbO~mCK{d=&$oU6~{AMix literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class new file mode 100644 index 0000000000000000000000000000000000000000..0ef542cfee5dce993de7c88f7b5ef06aea6cd9ad GIT binary patch literal 6382 zcmeHL-E!MR6h0e5?3k3ap=p5v6`_=bAL)iG(&N-hyY##Vqn4Nqy8 zd26V-k4{H{(CI!p-T%w!zH_=ia=P!FzUD=eY0qk&SdV)K4aA=IQQa&kdegUYK)wvhaoiNK z{-2CR6JGvT3=+$vEl>T&%owJMTs9^?8mGf8)zx(=v{7kjWd7ealjOj9Mtanx=-Og= zxa?o9a_z(^6K2W2}c55gi&L z#DNqdjq`GJR)=y_&Tpe0qsh#)4lKuGqMs@7(q++3Xg2(s4QD9FsCR3!80)un@(iJ8 zwLzN})v5P#@6K2t;36L!Gm*^fdOV^aj zOwTLrlRoy3FO9Ty#a?h}n;3{OJEmBmqHI*`j1z1@(3M&orHHO_A?{M$E9O_1u}xf9 zWiHn|4USdq;d&f6xHKyTI~rgMf} zn*=J@BDWmVll6_OV|(W)#Zg?s_QZf|jt1GX6Dl_sK>ZgR^3e68+@5V!MK zHtnXGGeuR{8xmOB6N0I_9epxgfsY6r&Uo%i;MRl#GBfCp3EVBPdIdfuaBTtrh2H15 zxfR_Oj)8FS$Rueb1e8;@s*_P-J0n>mQ}97KS(;ZCkh@8lqS zm5cD+6bOg82zRGIXyqU*pq;Jt-iI}J{Q&Oa72rcWYN#E7`>>AH;xl|+K)Mb11bn=o HgZqC1d~jIT literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class new file mode 100644 index 0000000000000000000000000000000000000000..37fe72ef0369b077b476c4d545f27caf698b8972 GIT binary patch literal 539 zcmb_ZOHKnZ41FoknSOxs6Bl4jR;-FxfQnkF&_yfuX~vqSq)9bNk$N>29DqY1Jj0@z zu89;ocKqyT`}^nf3%~%E2^s=Oa2jPPyMFQ^lU5% zT-U%=zS|Na(3+auskkXP>TbNBra%5;uq$^zuBGZf^aZX)PJ0(jh~(nXP@xLF*a~z< z&QbAXRR|OkbOk2=Je2*;c03cD@>urSCpZ&0u64jPUrY$b<+JUYf2upe6n_e76uTik=zYwnrGaO)h6(=hM>SW9bLtt`2#m8Ty5 zFlJx|zVns;hvB!9>m#;fyV#{27?1p<)ziE8y|=r6{PoLkM6^PAou)L}3WaS+9!l#8 zt1-(CDptsrF~4MO6`pWgYIH8-qUPFso5}KOAY$Ad{wx9kjgIG9ALONQ{nDaF=XWBIt?;a0 zb3?b_@g?66B$IC7hZ@c1*0_{h08uY-xm$KIn7_7=8?!#3pUQ0pz8`rW?oYHyx>e&s zxDLngbOsjE>4-*0TAHi~5r|njrqSACU+^&SYTPkk;i~J!Y8X`!K(ERjBb{onFl?r@ zGz#2kejx*c8M0pG=`_vLJ9Bh`PHHrB*Y#a_PotC9+GJ{2Z($FTco}rlpxo%O3PiBP$IzG+f_t1!k^SM<5ot;J%IE zEsCIm=VuY#IfiL_EDVjJ>+vw1dUaeJ4#rA=Dzl%m5;rbmQkh6w5D00@MA|Z&Y0He$ zmYJk2GfrEV4JY8Cq2^gdT?X>b4U_CNWiJG-I=k$Re?aTd4*@CU*C^>|yV2 zyhysRTrbu;gTqY8T2MnM79h>PlpeY`G+TGv>Jm{+)u5dGWl{nZqSxl=-NGGX#Xgw zc>|D^WKePKtS@Hw*Riu=ADQai8wz6Nms<95&NaE8UeLEZjH!PHLr zKHMY6>6)SJK1w*ZOZ&KrUhR#MHY50JE^Qtw6_4+!Ws1{o=ha4191wIvO^%_EUgbnw zXJL6Tzf9n%#D&T*S@YyvRHB3D_8V%!e;8b&ZKhYPirn0ccpJd`@G zpH(K;K4_HA2a&M(Dt03@I^AK+xI2!Q@z~AFyCt8=NC4~4T<^a5?$|fO#!ax_w%0vu zQJu~8SwrC6$*gZA)P+e==;fOta?n1=3Dxz0iC{> zsJ2Nz*P(4Pq|;rE>XXp+MQf9dx7%7(eusyVC)cH{qJ!0FK^bBwT%EqcR_^3QRibSN z>Imdqw`?z7>a?uUPm>oWsqM*x+-JG#-O}~N)@enf=v9fkPtdFV1nt1nXVBcqLybxk6{59H-(L}1Z*%VvzX2UeH}n7i literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..f0194e1fc24a253d7138dc3e0c9d38547d8596ac GIT binary patch literal 4182 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 literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..6a3a6a18b492e0e297a4abb7984a0dfc7fcb2fc4 GIT binary patch literal 10068 zcmeHN&2Jnv6o1}^>_6iSpLSSK8fVTg>%j$5pKBdV!36SNZo`HM_w==K4&tj`VrpN>g;1&$^mN zZeszTZp2E<&P~Ase?EQRM8qI$t@ zn;V?!?JlPVz%k05R5|rn7)njoIn6*Ex}>R4RD`~W(7s?Bt6Dtbp@$k;k?i2}S25Q4 z)bRqQ6m5xstH-Iu=cw2i&}H5|*5>pq_S`|9&>-Z=L7tqHCufHzC*jHI@pP8@k}GQZ z-eKC?pc-Ao?{~4o{jfL~95Wc4#BoC_Nqr(ZT^VUAqqc+I;*s98xcxDRCVdcckn&-~ zwCM0$c(E42K@c<~ZpJu02X)4bm9~`aV4!~nNG^4{f$&hFin~0*DIO{v#h#WCP3Q2; zMKfLF(B~}?nkkAkSY~m6Mm~^s=rTqnQ-e~Jg=Vy;z6wF?+gnK6z+IA+0cwn|H zIah{a{1$7KV^ka*{}*#nu~z?&V{`caz=_nL29py|hNA?I&(uxU>n6uC>5hNS9KB4| zF;`lNJL^1Znw>m>V|D4V;7)I6-+MWxH-sYaW&P2e9pSOiq{awka+coqoj`3TgHmNU zN#Jl_PDSAKY8;yUd@0-#N}%=g=BOt*971Eq>xR*}3cO6n-+7LZ4or9)>5PuRL^h1f zGEsro2>J6F{pR$vR((1b&U{uAIFb>cW*rqcOUPf(s86ZSorx7&ri-=8vAE~8yKnip zE@i-3Sb;YP`R*D2DgVU?D%NT+tprYHll3->6jvsrN!P6k%#hO5NYPfz%f(s^=BBzw zUfO?pyNB2#NM8w@&qmBL3-Ah{!~F+?rjENt)s!o75LDnTLVg&j@sn?Xe8LXt30%ks zUBH{lP$z+)(P}BK4YB3`B#Q_RAzzOaqs8WSu~vg4Lf_UZW8_@|3wtw?T+0v4sR&GG zqhLOY*mk3JI}#nC#TLgL?o?KnUIhNP@=R8_U~cd6vOE>)3k8_>W-@E{L{}|yjkn{P z5!h+F%5@xtYLzRzO;n9=RZ3mwTRb2zleJw;Zp~GurYWy;{NA9>lukAFjpUwa56&W4 zTa$6*@tgQT0Rj3D!kXx|1BV;s5cZ22*`3Poz@t0=$S%0LKyG5WIw? zF|0WO(^xX{r|@?-?r96|v=#T2p>SWd;GVPM(xGtASa2^{anB8f`?>}9vK9CIP`DQ? zxbs%ri$meQX~A8w;=Vl;?j;NE7gk(%DBO1}xZH~S-oA0A75Dvp<8E7VXZMZ!jTQHU zedB&>#r<&KxZhiGFC%VgK(w)qf*-AD9~DIV-GWwvk1_U@;0k<_#KIEH!HItXpW88+ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class new file mode 100644 index 0000000000000000000000000000000000000000..2d1bdef817f8ed0514a20a347e432115fa68c524 GIT binary patch literal 7715 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 P*=|5%@GX5m2Y229d>3~_ literal 0 HcmV?d00001 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt new file mode 100644 index 0000000000..32772d9ba7 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt @@ -0,0 +1,10 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt new file mode 100644 index 0000000000..436ae71527 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt @@ -0,0 +1,17 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt new file mode 100644 index 0000000000..7c8d4ab875 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt @@ -0,0 +1,18 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json +Content-Type: application/json +foo: bar + +{} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt new file mode 100644 index 0000000000..cb7449970a --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt @@ -0,0 +1,90 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"awesomeFunction":"awesomeStuff"}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"awesomeFunction":["foo","bar"]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"IntegerValue":33,"StringValue":"Alice"}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt new file mode 100644 index 0000000000..7a25853f38 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt @@ -0,0 +1,83 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST Entities HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"IntegerValue":12} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET Entities(9000) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 4 + +PATCH Entities(13) HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{} + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 5 + +DELETE Entities(14) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000003-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 6 + +DELETE Entities(15) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 7 + +DELETE Entities(16) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000004-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt new file mode 100644 index 0000000000..4864bce1eb --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt @@ -0,0 +1,90 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 +Accept: application/json +header-read_all: read_all + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST Entities HTTP/1.1 +Accept: application/json +Content-Type: application/json +header-create: create + +{"IntegerValue":12} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET Entities(9000) HTTP/1.1 +Accept: application/json +header-read_by_key: read_by_key + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 4 + +PATCH Entities(13) HTTP/1.1 +Accept: application/json +Content-Type: application/json +header-update: update + +{} + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 5 + +DELETE Entities(14) HTTP/1.1 +Accept: application/json +header-delete: delete-entity14 + + +--changeset_00000000-0000-0000-0000-000000000003-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 6 + +DELETE Entities(15) HTTP/1.1 +Accept: application/json +header-delete: delete-entity15 + + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 7 + +DELETE Entities(16) HTTP/1.1 +Accept: application/json +header-delete: delete-entity16 + + +--changeset_00000000-0000-0000-0000-000000000004-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt new file mode 100644 index 0000000000..68718fe227 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt @@ -0,0 +1,63 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt new file mode 100644 index 0000000000..040aa489ca --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt @@ -0,0 +1,62 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1447}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt new file mode 100644 index 0000000000..7da32711f4 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt @@ -0,0 +1,29 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt new file mode 100644 index 0000000000..cca2d42f9b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt @@ -0,0 +1,19 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/pom.xml b/datamodel/odata-core-apache-httpclient5/pom.xml new file mode 100644 index 0000000000..04351015c3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + com.sap.cloud.sdk.datamodel + datamodel-parent + 5.35.0-SNAPSHOT + + odata-core-apache-httpclient5 + jar + Data Model - OData Services - Core (HttpClient 5) + OData Services data model (VDM) - core classes using Apache HttpClient 5. + https://sap.github.io/cloud-sdk/docs/java/getting-started + + SAP SE + https://www.sap.com + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + SAP + cloudsdk@sap.com + SAP SE + https://www.sap.com + + + + false + + + + com.sap.cloud.sdk.datamodel + odata-client-apache-httpclient5 + ${project.version} + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-core + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-connectivity + + + com.sap.cloud.sdk.cloudplatform + connectivity-apache-httpclient5 + + + com.sap.cloud.sdk.datamodel + fluent-result + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + com.google.code.gson + gson + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.client5 + httpclient5 + + + io.vavr + vavr + + + + org.projectlombok + lombok + provided + + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + org.wiremock + wiremock + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.java new file mode 100644 index 0000000000..62efe10b08 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.java @@ -0,0 +1,40 @@ +package com.sap.cloud.sdk.datamodel.odata.adapter; + +import java.lang.reflect.Type; +import java.math.BigDecimal; + +import javax.annotation.Nonnull; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * GSON serializer that transforms numbers to their JSON representation according to the OData V2 standard. Meant for + * internal use only. + */ +public class ODataNumberSerializer implements JsonSerializer +{ + @Override + @Nonnull + public JsonElement serialize( + @Nonnull final Number src, + @Nonnull final Type typeOfSrc, + @Nonnull final JsonSerializationContext context ) + { + /* + Short is used both for Edm.Byte and Edm.Int16. + Edm.Byte should be a string but Edm.Int16 should be a number. + But we can't differentiate between that here because we only know it's a Short. + So we serialize to the plain number because this worked in the past. + */ + if( typeOfSrc == Integer.class || typeOfSrc == Short.class ) { + return new JsonPrimitive(src); + } else if( typeOfSrc == BigDecimal.class ) { + return new JsonPrimitive(((BigDecimal) src).toPlainString()); + } else { + return new JsonPrimitive(src.toString()); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.java new file mode 100644 index 0000000000..572b3d661f --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.java @@ -0,0 +1,32 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +/** + * Representation of any OData function import as a fluent class for further configuring the request and + * {@link #executeRequest(Destination) executing} it.This is specifically for functions that return either a collection + * of primitive values or entities + * + * @param + * The fluent helper type. + * @param + * The type of the object this OData request operates on, if any. + * @param + * The type of the result entity, if any. + */ +public abstract class CollectionValuedFluentHelperFunction + extends + FluentHelperFunction +{ + + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + */ + public CollectionValuedFluentHelperFunction( @Nonnull final String servicePath ) + { + super(servicePath); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.java new file mode 100644 index 0000000000..27a63b2432 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.java @@ -0,0 +1,219 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldUntyped; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionString; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueString; +import com.sap.cloud.sdk.typeconverter.TypeConverter; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +/** + * Template class to represent entity fields. Instances of this object are used in query modifier methods of the entity + * fluent helpers. Contains methods to compare a field's value with a provided value. + *

+ * Use the constants declared in each entity inner class. Instantiating directly requires knowing the underlying OData + * field names, so use the constructor with caution. + * + * @param + * VdmObject that the field belongs to + * @param + * Field type + */ +@EqualsAndHashCode +public class EntityField implements EntitySelectable +{ + @Nonnull + @Getter + private final String fieldName; + + @Nullable + @Getter + private final TypeConverter typeConverter; + + @Nonnull + private final FieldUntyped fieldUntyped; + + /** + * Use the constants declared in each entity inner class. Instantiating directly requires knowing the underlying + * OData field names, so use with caution. + * + * @param fieldName + * OData field name. Must match the field returned by the underlying OData service. + */ + public EntityField( @Nonnull final String fieldName ) + { + this(fieldName, null); + } + + /** + * Use the constants declared in each entity inner class. Instantiating directly requires knowing the underlying + * OData field names, so use with caution. + *

+ * When creating instances for custom fields, this constructor can be used to add a type converter that will be + * automatically used by the respective entity when getting or setting custom fields. + * + * @param fieldName + * OData field name. Must match the field returned by the underlying OData service. + * @param typeConverter + * An implementation of a TypeConverter. The first type must match FieldT, the second type must match the + * type Olingo returns. + */ + public EntityField( @Nonnull final String fieldName, @Nullable final TypeConverter typeConverter ) + { + this.fieldName = fieldName; + this.typeConverter = typeConverter; + fieldUntyped = FieldReference.of(fieldName); + } + + /** + * Equals-null expression fluent helper. + * + * @return Fluent helper that represents a field == null expression. + */ + @Nonnull + public ExpressionFluentHelper eqNull() + { + return new ExpressionFluentHelper<>(fieldUntyped.equalToNull()); + } + + /** + * Equals expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field == value expression. + */ + @Nonnull + public ExpressionFluentHelper eq( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.equalTo(value)); + } + + /** + * Not equals-null expression fluent helper. + * + * @return Fluent helper that represents a field != null expression. + */ + @Nonnull + public ExpressionFluentHelper neNull() + { + return new ExpressionFluentHelper<>(fieldUntyped.notEqualToNull()); + } + + /** + * Not equals expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field != value expression. + */ + @Nonnull + public ExpressionFluentHelper ne( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.notEqualTo(value)); + } + + /** + * Greater than expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field > value expression. + */ + @Nonnull + public ExpressionFluentHelper gt( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.greaterThan(value)); + } + + /** + * Greater than or equals expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field ≥ value expression. + */ + @Nonnull + public ExpressionFluentHelper ge( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.greaterThanEqual(value)); + } + + /** + * Less than expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field < value expression. + */ + @Nonnull + public ExpressionFluentHelper lt( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.lessThan(value)); + } + + /** + * Less than or equals expression fluent helper. + * + * @param value + * Field value to compare with. + * + * @return Fluent helper that represents a field ≤ value expression. + */ + @Nonnull + public ExpressionFluentHelper le( @Nullable final FieldT value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.lessThanEqual(value)); + } + + /** + * Expression fluent helper supporting the filter function "substringof". + * + * @param value + * value String value to apply the function on + * + * @return Fluent helper that represents a {@code substringof(value,field)} expression + */ + @Nonnull + public ExpressionFluentHelper substringOf( @Nonnull final String value ) + { + return new ExpressionFluentHelper<>( + FilterExpressionString.substringOf(ValueString.literal(value), fieldUntyped.asString())); + } + + /** + * Expression fluent helper supporting the filter function "endswith". + * + * @param value + * String value to apply the function on + * @return Fluent helper that represents a {@code endswith(field,value)} expression + */ + @Nonnull + public ExpressionFluentHelper endsWith( @Nonnull final String value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.asString().endsWith(value)); + } + + /** + * Expression fluent helper supporting the filter function "startswith". + * + * @param value + * String value to apply the function on + * @return Fluent helper that represents a {@code startswith(field,value)} expression + */ + @Nonnull + public ExpressionFluentHelper startsWith( @Nonnull final String value ) + { + return new ExpressionFluentHelper<>(fieldUntyped.asString().startsWith(value)); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.java new file mode 100644 index 0000000000..a45d275c1d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.java @@ -0,0 +1,50 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * OData selector for entities to select each and every field, i.e. star selector. Instances of this object are used in + * query modifier methods of the entity fluent helpers. Methods to compare with provided values are not supported. + * + * @param + * VdmObject that the field belongs to + */ +public class EntityFieldAll> implements EntitySelectable +{ + @Nonnull + @Override + public String getFieldName() + { + return "*"; + } + + @Nonnull + @Override + public List getSelections() + { + return Collections.singletonList("*"); + } + + @Nonnull + @Override + public String toString() + { + return "*"; + } + + @Override + public boolean equals( @Nullable final Object o ) + { + return o instanceof EntitySelectable && ((EntitySelectable) o).getSelections().equals(getSelections()); + } + + @Override + public int hashCode() + { + return getSelections().hashCode(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.java new file mode 100644 index 0000000000..f4232eb038 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.java @@ -0,0 +1,180 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.collect.Lists; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * Helper class for representing links (navigation properties) between entities. + * + * @param + * The type of this link. + * @param + * The type of the entity. + * @param + * The type of the subentity. + */ +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +public class EntityLink, EntityT extends VdmObject, SubEntityT extends VdmObject> + implements + EntitySelectable +{ + private static final String STAR_SELECTOR = "*"; + + private final List> descendants = new ArrayList<>(); + private final List> selectors = new ArrayList<>(); + + @Nonnull + @Getter + private final String fieldName; + + private EntityLink( + @Nonnull final EntityLink toCopy, + @Nullable final Iterable> addDescendants, + @Nullable final Iterable> addSelectors ) + { + this(toCopy.fieldName); + descendants.addAll(toCopy.descendants); + selectors.addAll(toCopy.selectors); + + if( addDescendants != null ) { + descendants.addAll(Lists.newArrayList(addDescendants)); + } + + if( addSelectors != null ) { + selectors.addAll(Lists.newArrayList(addSelectors)); + } + } + + /** + * Copy constructor. + * + * @param toCopy + * The link to copy. + */ + protected EntityLink( @Nonnull final EntityLink toCopy ) + { + this(toCopy, null, null); + } + + /** + * Used in combination with {@link FluentHelperRead#select(Object[]) FluentHelperRead.select} when expanding a + * navigation property to specify which fields of that navigation property to select, and which navigation + * properties of that navigation property to expand. + * + * @param selectors + * Array of fields to select and/or navigation properties to expand. + * @return Selector for {@link FluentHelperRead#select(Object[]) FluentHelperRead.select}. + */ + @SuppressWarnings( { "unchecked", "varargs" } ) + @SafeVarargs + @Nonnull + public final LinkT select( @Nonnull final EntitySelectable... selectors ) + { + final List> additionalDescendants = new ArrayList<>(); + final List> additionalSelectors = new ArrayList<>(); + + for( final EntitySelectable select : selectors ) { + if( select instanceof EntityLink ) { + additionalDescendants.add((EntityLink) select); + } else { + additionalSelectors.add(select); + } + } + + final EntityLink toTranslate = + new EntityLink<>(this, additionalDescendants, additionalSelectors); + + return translateLinkType(toTranslate); + } + + /* + * may not be not required, since getSelections() provide implicit expand definitions. + * E.g. select(to_BusinessPartnerRole/*) => expand(to_BusinessPartner) + */ + + /** + * Returns a list of expansions for this link. + * + * @return A list of expansions for this link. + */ + @Nonnull + public List getExpansions() + { + if( descendants.isEmpty() ) { + return Lists.newArrayList(getFieldName()); + } + + final List result = new ArrayList<>(); + for( final EntityLink descendant : descendants ) { + for( final String name : descendant.getExpansions() ) { + result.add(getFieldName() + "/" + name); + } + } + return result; + } + + @Nonnull + @Override + public List getSelections() + { + final List result = new ArrayList<>(); + if( selectors.isEmpty() && descendants.isEmpty() ) { + result.add(getFieldName() + "/" + STAR_SELECTOR); + } + for( final EntitySelectable field : selectors ) { + result.add(getFieldName() + "/" + field.getFieldName()); + } + + for( final EntityLink descendant : descendants ) { + for( final String name : descendant.getSelections() ) { + result.add(getFieldName() + "/" + name); + } + } + return result; + } + + /** + * Returns the given {@code link} in a type-safe manner. + * + * @param link + * The link to cast. + * @return The given {@code link} in a type-safe manner. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + protected LinkT translateLinkType( final EntityLink link ) + { + return (LinkT) link; + } + + /** + * Add a filter expression on a single navigation property. + * + * @param filterExpression + * The filter to apply. + * @return A new expression builder that includes the given filter. + */ + @Nonnull + protected ExpressionFluentHelper filterOnOneToOneLink( + @Nonnull final ExpressionFluentHelper filterExpression ) + { + final ValueBoolean exp = + ( protocol, prefixes ) -> getFieldName() + + "/" + + filterExpression.getDelegateExpressionWithoutOuterParentheses().getExpression(protocol, prefixes); + return new ExpressionFluentHelper<>(exp); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.java new file mode 100644 index 0000000000..833acdcba1 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.java @@ -0,0 +1,37 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.google.common.collect.Lists; + +/** + * Interface to enable management of OData entity selectors. This interface is used by + * {@link com.sap.cloud.sdk.datamodel.odata.helper.EntityField EntityField} and + * {@link com.sap.cloud.sdk.datamodel.odata.helper.EntityLink EntityLink}. + * + * @param + * The generic entity type. + */ +public interface EntitySelectable +{ + /** + * Get the field name of OData entity property. + * + * @return The field name + */ + @Nonnull + String getFieldName(); + + /** + * Get the select expression that represents the OData entity property. + * + * @return The serialized select terms. + */ + @Nonnull + default List getSelections() + { + return Lists.newArrayList(getFieldName()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.java new file mode 100644 index 0000000000..49655ee1c3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.java @@ -0,0 +1,156 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; + +import lombok.extern.slf4j.Slf4j; + +/** + * Template class that represents query expressions. Instances of this object are used in query modifier methods of the + * entity fluent helpers. + * + * Use either the expression methods from EntityField instances, or the logical operators + * {@link #and(ExpressionFluentHelper) and} and {@link #or(ExpressionFluentHelper) or} as methods in this class. + * Negation can be achieved by {@link #not() not}. Every logical operator creates and returns a new instance based on + * the original expression object. Instantiating objects from this class directly can cause undefined results. + * + * @see ExpressionFluentHelper#not(ExpressionFluentHelper) + * + * @param + * VdmObject that the expression is operating on. + */ +@Slf4j +public class ExpressionFluentHelper +{ + private final ValueBoolean delegateExpression; + + /** + * Creates a new helper based on an arbitrary, untyped filter expression. + *

+ * Instances of this class can be used to pass an unchecked {@link ValueBoolean} to fluent helpers. This approach + * discards type safety and is generally discouraged. + * + * @param delegateExpression + * The expression to delegate to. + */ + public ExpressionFluentHelper( @Nonnull final ValueBoolean delegateExpression ) + { + this.delegateExpression = delegateExpression; + } + + /** + *

+ * Boolean OR expression fluent helper. + *

+ *

+ * Please note:
+ * Filter expressions chained together by logical operators are interpreted in the same order as their corresponding + * methods are called. Since the Java language evaluates method calls from left to right, the fluent API design is + * following the same principle. The implicit precedence is following the method invocation and not the underlying, + * logical operators: + * + *

+     *     A.or(B).and(C) <=> (A.or(B)).and(C)
+     * 
+ * + *

+ *

+ * Recommendation:
+ * Incorporate parentheses or introduce variables to reflect combined expressions: + * + *

+     *     var AorB = A.or(B)
+     *     AorB.and(C)
+     * 
+ * + *

+ * + * + * @param disjunctExpression + * Other expression to combine with. + * + * @return Fluent helper that represents a ((this) || other) expression. + */ + @Nonnull + public ExpressionFluentHelper or( @Nonnull final ExpressionFluentHelper disjunctExpression ) + { + return new ExpressionFluentHelper<>(delegateExpression.or(disjunctExpression.delegateExpression)); + } + + /** + *

+ * Boolean AND expression fluent helper. + *

+ *

+ * Please note:
+ * Filter expressions chained together by logical operators are interpreted in the same order as their corresponding + * methods are called. Since the Java language evaluates method calls from left to right, the fluent API design is + * following the same principle. The implicit precedence is following the method invocation and not the underlying, + * logical operators: + * + *

+     *     A.or(B).and(C) <=> (A.or(B)).and(C)
+     * 
+ * + *

+ *

+ * Recommendation:
+ * Incorporate parentheses or introduce variables to reflect combined expressions: + * + *

+     *     var AorB = A.or(B)
+     *     AorB.and(C)
+     * 
+ * + *

+ * + * @param conjunctExpression + * Other expression to combine with. + * + * @return Fluent helper that represents a ((this) && other) expression. + */ + @Nonnull + public ExpressionFluentHelper and( @Nonnull final ExpressionFluentHelper conjunctExpression ) + { + return new ExpressionFluentHelper<>(delegateExpression.and(conjunctExpression.delegateExpression)); + } + + /** + * Boolean NOT expression fluent helper. + * + * @return Fluent helper that represents a not(this) expression. + */ + @Nonnull + public ExpressionFluentHelper not() + { + return new ExpressionFluentHelper<>(delegateExpression.not()); + } + + /** + * Boolean NOT expression fluent helper. + * + * @param + * The type argument for the returned {@link ExpressionFluentHelper} + * @param expression + * expression to be negated. + * + * @return Fluent helper that represents a not(expression) expression. + */ + @Nonnull + public static ExpressionFluentHelper not( @Nonnull final ExpressionFluentHelper expression ) + { + return expression.not(); + } + + @Nonnull + ValueBoolean getDelegateExpressionWithoutOuterParentheses() + { + return ( prefixes, protocol ) -> { + final String expression = delegateExpression.getExpression(prefixes, protocol); + return expression.startsWith("(") && expression.endsWith(")") + ? expression.substring(1, expression.length() - 1) + : expression; + }; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java new file mode 100644 index 0000000000..46b5f40bc9 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java @@ -0,0 +1,234 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataUriFactory; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of any OData request as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the entity this OData request operates on, if any. + * @param + * The type of the result entity, if any. + */ +@Slf4j +public abstract class FluentHelperBasic implements FluentHelperExecutable +{ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final String servicePath; + + /** + * The entity collection to send the OData requests to + */ + @Nullable + protected String entityCollection = null; + + /** + * A map containing the headers to be used for all explicit and implicit requests that are part of this FluentHelper + * implementation (csrf). + */ + private final Map headers = new LinkedHashMap<>(); + + /** + * A map containing the custom query parameters to be used only for the actual request of this FluentHelper + * implementation. + */ + @Getter( AccessLevel.PROTECTED ) + private final Map parametersForRequestOnly = new LinkedHashMap<>(); + + /** + * Returns a class object of the type this fluent helper works with. + * + * @return A class object of the handled type. + */ + @Nonnull + protected abstract Class getEntityClass(); + + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperBasic( @Nonnull final String servicePath, @Nullable final String entityCollection ) + { + this.servicePath = servicePath; + this.entityCollection = entityCollection; + } + + /** + * Returns the current fluent helper instance. + * + * @return The current fluent helper instance. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + protected FluentHelperT getThis() + { + return (FluentHelperT) this; + } + + @Nullable + @Override + public abstract ResultT executeRequest( @Nonnull final Destination destination ); + + /** + * Get all headers for explicit and implicit requests. + * + * @return a map containing the headers for actual request and implicit requests, e.g. csrf token request. A map + * containing the headers to be used for all implicit requests that are part of this FluentHelper + * implementation (csrf, ...). + */ + protected Map getHeaders() + { + return headers; + } + + /** + * Gives the option to specify custom HTTP headers. The returned object allows to specify the requests the headers + * should be used in. + * + * @param key + * Name of the (first) desired HTTP header parameter. + * @param value + * Value of the (first) desired HTTP header parameter. + * + * @return A fluent helper to specify further headers and their intended usage. + */ + @Nonnull + public FluentHelperT withHeader( @Nonnull final String key, @Nullable final String value ) + { + headers.put(key, value); + return getThis(); + } + + /** + * Gives the option to specify a map of custom HTTP headers. The returned object allows to specify the requests the + * headers should be used in. + * + * @param map + * A map of HTTP header key/value pairs. + * @return A fluent helper to specify further headers and their intended usage. + */ + @Nonnull + public FluentHelperT withHeaders( @Nonnull final Map map ) + { + headers.putAll(map); + return getThis(); + } + + /** + * Gives the option to specify custom query parameters for the request. The passed parameter value will be encoded + * with percentage encoding. + * + *

+ * Note: It is recommended to only use this function for query parameters which are not supported + * by the VDM by default. Using this function to bypass fluent helper method calls can lead to unsupported response + * handling. There is no contract on the order or priority of parameters added to the query. + *

+ * + *

+ * Example: Use the query option $search to reduce the result set, leaving only + * entities which match the specified search expression. This feature is supported in protocol OData v4. + * + *

+     * new DefaultBusinessPartnerService().getAllBusinessPartner().withQueryParameter("$search", "Köln OR Cologne")
+     * 
+ *

+ * + * @param key + * Name of the query parameter. + * @param value + * Unencoded value of the query parameter. + * + * @return The same fluent helper. + */ + @Nonnull + protected FluentHelperT withQueryParameter( @Nonnull final String key, @Nullable final String value ) + { + parametersForRequestOnly.put(key, value); + return getThis(); + } + + /** + * Query modifier to limit which field values of the entity get fetched and populated, and to specify which + * navigation properties to expand. If this method is never called, then all fields will be fetched and populated, + * and no navigation properties expanded. But if this method is called at least once, then only the specified fields + * will be fetched and populated. Calling this multiple times will combine the set(s) of fields and expansions of + * each call. + * + * @param fields + * Fields to select and/or navigation properties to expand. + * + * @param delegateSelect + * Handler to accept simple property names being put inside a delegate "select" query option. + * @param delegateExpand + * Handler to accept navigation properties being put inside a delegate "expand" query option. + * @return The same fluent helper with this query modifier applied. + */ + @Nonnull + FluentHelperT select( + @Nonnull final Iterable> fields, + @Nonnull final Consumer delegateSelect, + @Nonnull final Consumer delegateExpand ) + { + for( final EntitySelectable field : fields ) { + final List selections = field.getSelections(); + + // add items to selections + selections.forEach(delegateSelect); + + // add items to expansions + for( final String fieldName : selections ) { + final int lastSlash = fieldName.lastIndexOf("/"); + if( lastSlash > 0 ) { + final String expandString = fieldName.substring(0, lastSlash); + final StructuredQuery q = StructuredQuery.asNestedQueryOnProperty(expandString, ODataProtocol.V2); + delegateExpand.accept(q); + } + } + } + + return getThis(); + } + + /** + * Translate this OData v2 request into a OData request object extending {@link ODataRequestGeneric}. + * + * @return A protocol agnostic OData request instance. + */ + @Nonnull + public abstract ODataRequestGeneric toRequest(); + + @Nonnull + RequestT addHeadersAndCustomParameters( @Nonnull final RequestT request ) + { + getHeaders().forEach(request::addHeader); + + getParametersForRequestOnly() + .forEach(( key, value ) -> request.addQueryParameter(key, ODataUriFactory.encodeQuery(value))); + + return request; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java new file mode 100644 index 0000000000..c23a2b6116 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java @@ -0,0 +1,113 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Arrays; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +/** + * Representation of an OData request to retrieve an entity by its key as a fluent interface for further configuring the + * request and {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the result entity. + * @param + * The type of the class that represents fields of the entity. + */ +public abstract class FluentHelperByKey, SelectableT> + extends + FluentHelperBasic +{ + private final StructuredQuery delegateQuery; + + /** + * Instantiates this fluent helper using the given service path and entity collection to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperByKey( @Nonnull final String servicePath, @Nonnull final String entityCollection ) + { + super(servicePath, entityCollection); + delegateQuery = StructuredQuery.onEntity(entityCollection, ODataProtocol.V2); + } + + /** + * Getter for a map containing the OData name of key properties, each mapped to the value to search by. + * + * @return A name-value mapping for the OData key properties. + */ + @Nonnull + protected abstract Map getKey(); + + @Override + @Nonnull + public ODataRequestReadByKey toRequest() + { + final ODataEntityKey entityKey = ODataEntityKey.of(getKey(), ODataProtocol.V2); + final String queryString = delegateQuery.getEncodedQueryString(); + + final ODataRequestReadByKey request = + new ODataRequestReadByKey(getServicePath(), entityCollection, entityKey, queryString, ODataProtocol.V2); + + return super.addHeadersAndCustomParameters(request); + } + + @Override + @Nonnull + public FluentHelperT withQueryParameter( @Nonnull final String key, @Nullable final String value ) + { + return super.withQueryParameter(key, value); + } + + /** + * Query modifier to limit which field values of the entity get fetched & populated. If this method is never + * called, then all fields will be fetched & populated. But if this method is called at least once, then only + * the specified fields will be fetched & populated. Calling this multiple times will combine the set(s) of + * fields of each call. + * + * @param fields + * Array of fields to select. + * + * @return The same fluent helper with the provided fields selected. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + public FluentHelperT select( @Nonnull final SelectableT... fields ) + { + final Iterable> selectableFields = (Iterable>) Arrays.asList(fields); + return super.select(selectableFields, delegateQuery::select, delegateQuery::select); + } + + @Override + @Nonnull + public EntityT executeRequest( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + final EntityT result = response.as(getEntityClass()); + + // use version identifier from header if present + response.getVersionIdentifierFromHeader().peek(result::setVersionIdentifier); + + result.attachToService(getServicePath(), destination); + return result; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java new file mode 100644 index 0000000000..3a2e9a79ea --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java @@ -0,0 +1,71 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCount; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +/** + * Representation of an OData query for count, as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + */ +@RequiredArgsConstructor( access = AccessLevel.PACKAGE ) +public class FluentHelperCount +{ + private final ODataRequestCount request; + + /** + * Executes the underlying query for count, using the stored values, plus any query modifiers that were previously + * called. + * + * @param destination + * The target system this request should be issued against. + * @return The number of tuples that match the criteria specified in the query. + * + * @throws DestinationAccessException + * If there is an issue accessing the + * {@link com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination}. + * @throws HttpClientInstantiationException + * If there is an issue creating the {@link HttpClient}. + * @throws com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException + * If the OData request execution failed. Please find the documentation for {@link ODataException} + * possible sub-types and error scenarios they can occur in. + */ + public long executeRequest( @Nonnull final Destination destination ) + { + final ODataRequestCount requestCount = toRequest(); + + final ODataRequestResultGeneric result = + requestCount.execute(ApacheHttpClient5Accessor.getHttpClient(destination)); + + return result.as(Long.class); + } + + /** + * Creates an instance of {@link ODataRequestCount}. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the OData query
  • + *
+ * + * @return A new count request with the given configuraiton. + */ + @Nonnull + public ODataRequestCount toRequest() + { + return request; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.java new file mode 100644 index 0000000000..240235d30d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.java @@ -0,0 +1,148 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataSerializationException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData create request as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the entity to create. + */ +@Slf4j +public abstract class FluentHelperCreate> + extends + FluentHelperModification +{ + private EntityLink, ?, EntityT> linkFromParentEntity; + private VdmEntity parentEntity; + + /** + * Instantiates this fluent helper using the given service path and entity collection to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperCreate( @Nonnull final String servicePath, @Nonnull final String entityCollection ) + { + super(servicePath, entityCollection); + } + + /** + * Getter for the VDM representation of the entity to be created. + * + * @return The entity that should be created by calling the {@link #executeRequest(Destination)} method. + */ + @Nonnull + protected abstract EntityT getEntity(); + + @SuppressWarnings( "unchecked" ) + @Override + @Nonnull + protected Class getEntityClass() + { + return (Class) getEntity().getClass(); + } + + @Override + @Nonnull + public ModificationResponse executeRequest( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric result = toRequest().execute(httpClient); + + return ModificationResponse.of(result, getEntity(), destination); + } + + @Override + @Nonnull + public ODataRequestCreate toRequest() + { + final EntityT entity = getEntity(); + final ODataResourcePath resourcePath; + + if( linkFromParentEntity != null && parentEntity != null ) { + resourcePath = + ODataResourcePath + .of(parentEntity.getEntityCollection(), ODataEntityKey.of(parentEntity.getKey(), ODataProtocol.V2)) + .addSegment(linkFromParentEntity.getFieldName()); + } else { + resourcePath = ODataResourcePath.of(getEntityCollection()); + } + + final String serializedEntity = + Try + .of(() -> ODataEntitySerializer.serializeEntityForCreate(entity)) + .getOrElseThrow( + e -> new ODataSerializationException( + new ODataRequestCreate(getServicePath(), resourcePath, "", ODataProtocol.V2), + entity, + "Failed to serialize HTTP request entity of type " + getEntityClass().getSimpleName(), + e)); + + final ODataRequestCreate request = + new ODataRequestCreate(getServicePath(), resourcePath, serializedEntity, ODataProtocol.V2); + + return super.addHeadersAndCustomParameters(request); + } + + /** + * This function allows to create a new entity via an existing parent entity. Parent means that the existing entity + * has to be related to the entity to be created via a navigation property. Thus, the function requires the caller + * to provide an {@code EntityLink} that represents such a navigation property. + * {@code ParentEntityT} can represent any entity that is related to the entity to be created. {@code EntityT} + * represents the type of the entity to be created. Furthermore, the function requires an instance of type + * {@code ParentEntityT}. This instance must hold the key fields used to identify it. + * + * NOTE: While any EntityLink provided by the OData VDM satisfying these type constraints allows you to call this + * function, the service may NOT allow this kind of create operation for the respective navigation property. Thus, + * calling this function without knowledge of the underlying OData service can result in failing requests. + * + * @param entity + * An instance of the related entity that MUST hold values for the respective key fields. + * @param entityLink + * An {@link EntityLink} representing a navigation property. + * + * @return The same fluent helper configured to use the provided navigation property for creation. + * @param + * The generic parent entity type in this navigation property relation. + */ + @Nonnull + public > FluentHelperT asChildOf( + @Nullable final ParentEntityT entity, + @Nullable final EntityLink, ParentEntityT, EntityT> entityLink ) + { + linkFromParentEntity = entityLink; + parentEntity = entity; + + return getThis(); + } + + @Nonnull + private String getEntityCollection() + { + return Option.of(entityCollection).getOrElse(() -> getEntity().getEntityCollection()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.java new file mode 100644 index 0000000000..150c1b3cbf --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.java @@ -0,0 +1,128 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ETagSubmissionStrategy; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestDelete; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; + +/** + * Representation of an OData delete request as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the entity to delete. + */ +public abstract class FluentHelperDelete> + extends + FluentHelperModification +{ + private ETagSubmissionStrategy eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ETAG_FROM_ENTITY; + + /** + * Instantiates this fluent helper using the given service path and entity collection to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperDelete( @Nonnull final String servicePath, @Nonnull final String entityCollection ) + { + super(servicePath, entityCollection); + } + + /** + * The entity object to be deleted by calling the {@link #executeRequest(Destination)} method. + * + * @return The entity to be deleted. + */ + @Nonnull + protected abstract EntityT getEntity(); + + @SuppressWarnings( "unchecked" ) + @Override + @Nonnull + protected Class getEntityClass() + { + return (Class) getEntity().getClass(); + } + + /** + * The delete request will ignore any version identifier present on the entity and not send an `If-Match` header. + *

+ * Warning: This might lead to a response from the remote system that the `If-Match` header is missing. + *

+ * It depends on the implementation of the remote system whether the `If-Match` header is expected. + * + * @return The same request builder that will not send the `If-Match` header in the update request + */ + @Nonnull + public FluentHelperT disableVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_NO_ETAG; + return getThis(); + } + + /** + * The delete request will ignore any version identifier present on the entity and delete the entity, regardless of + * any changes on the remote entity. + *

+ * Warning: Be careful with this option, as this might overwrite any changes made to the remote + * representation of this object. + * + * @return The same request builder that will ignore the version identifier of the entity to update + */ + @Nonnull + public FluentHelperT matchAnyVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ANY_MATCH_ETAG; + return getThis(); + } + + @Override + @Nonnull + public ModificationResponse executeRequest( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric result = toRequest().execute(httpClient); + + return ModificationResponse.of(result, getEntity(), destination); + } + + @Override + @Nonnull + public ODataRequestDelete toRequest() + { + final EntityT entity = getEntity(); + final String versionIdentifier = + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()); + + final ODataRequestDelete request = + new ODataRequestDelete( + getServicePath(), + getEntityCollection(), + ODataEntityKey.of(entity.getKey(), ODataProtocol.V2), + versionIdentifier, + ODataProtocol.V2); + + return super.addHeadersAndCustomParameters(request); + } + + @Nonnull + private String getEntityCollection() + { + return Option.of(entityCollection).getOrElse(() -> getEntity().getEntityCollection()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.java new file mode 100644 index 0000000000..5d7e2a7864 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.java @@ -0,0 +1,38 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; + +/** + * Representation of any OData V2 request that can be executed. + * + * @param + * The type of the result entity, if any. + */ +public interface FluentHelperExecutable +{ + /** + * Executes this request. + * + * @param destination + * The target system this request should be issued against. + * @return A response according to the query criteria. + * + * @throws DestinationAccessException + * If there is an issue accessing the + * {@link com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination}. + * @throws HttpClientInstantiationException + * If there is an issue creating the {@link HttpClient}. + * @throws ODataException + * If the OData request execution failed. + */ + @Nullable + ResultT executeRequest( @Nonnull final Destination destination ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.java new file mode 100644 index 0000000000..e88436afed --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.java @@ -0,0 +1,280 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +/** + * Utility class to conveniently create fluent helper instances. + */ +@RequiredArgsConstructor( access = AccessLevel.PRIVATE ) +class FluentHelperFactory +{ + @Nonnull + private final String servicePath; + + static FluentHelperFactory withServicePath( @Nonnull final String servicePath ) + { + return new FluentHelperFactory(servicePath); + } + + < + FluentHelperT extends FluentHelperByKey, EntityT extends VdmEntity, SelectableT> + FluentHelperByKey + readByKey( + @Nonnull final Class entityClass, + @Nonnull final String entityCollection, + @Nonnull final Map key ) + { + return new FluentHelperByKey<>(servicePath, entityCollection) + { + @Nonnull + @Override + protected Map getKey() + { + return key; + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return entityClass; + } + }; + } + + < + FluentHelperT extends FluentHelperRead, EntityT extends VdmEntity, SelectableT> + FluentHelperRead + read( @Nonnull final Class entityClass, @Nonnull final String entityCollection ) + { + return new FluentHelperRead<>(servicePath, entityCollection) + { + @Nonnull + @Override + protected Class getEntityClass() + { + return entityClass; + } + }; + } + + < + FluentHelperT extends FluentHelperCreate, EntityT extends VdmEntity> + FluentHelperCreate + create( @Nonnull final String entityCollection, @Nonnull final EntityT entity ) + { + return new FluentHelperCreate<>(servicePath, entityCollection) + { + @Nonnull + @Override + protected EntityT getEntity() + { + return entity; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) entity.getClass(); + } + }; + } + + < + FluentHelperT extends FluentHelperCreate, EntityT extends VdmEntity> + FluentHelperCreate + create( @Nonnull final EntityT entity ) + { + return create(entity.getEntityCollection(), entity); + } + + < + FluentHelperT extends FluentHelperDelete, EntityT extends VdmEntity> + FluentHelperDelete + delete( @Nonnull final String entityCollection, @Nonnull final EntityT entity ) + { + return new FluentHelperDelete<>(servicePath, entityCollection) + { + @Nonnull + @Override + protected EntityT getEntity() + { + return entity; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) entity.getClass(); + } + }; + } + + < + FluentHelperT extends FluentHelperDelete, EntityT extends VdmEntity> + FluentHelperDelete + delete( @Nonnull final EntityT entity ) + { + return delete(entity.getEntityCollection(), entity); + } + + < + FluentHelperT extends FluentHelperUpdate, EntityT extends VdmEntity> + FluentHelperUpdate + update( @Nonnull final String entityCollection, @Nonnull final EntityT entity ) + { + return new FluentHelperUpdate<>(servicePath, entityCollection) + { + @Nonnull + @Override + protected EntityT getEntity() + { + return entity; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) entity.getClass(); + } + }; + } + + < + FluentHelperT extends FluentHelperUpdate, EntityT extends VdmEntity> + FluentHelperUpdate + update( @Nonnull final EntityT entity ) + { + return update(entity.getEntityCollection(), entity); + } + + < + FluentHelperT extends FluentHelperFunction, ObjectT, ResultT> + FluentHelperFunction + function( + @Nonnull final Map parameters, + @Nonnull final String functionName, + @Nonnull final Class objectClass, + @Nonnull final Function requestHandler, + @Nonnull final BiFunction, Destination, ResultT> executeHandler ) + { + return new FluentHelperFunction<>(servicePath) + { + @Nonnull + @Override + protected Map getParameters() + { + return parameters; + } + + @Nonnull + @Override + protected String getFunctionName() + { + return functionName; + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return requestHandler.apply(uri); + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return objectClass; + } + + @Nullable + @Override + public ResultT executeRequest( @Nonnull final Destination destination ) + { + return executeHandler.apply(this, destination); + } + + /** + * {@inheritDoc} The special treatment for SAP S/4 HANA OData v2 responses is considered. + */ + @Override + @Nullable + protected JsonElement refineJsonResponse( @Nullable JsonElement jsonElement ) + { + if( jsonElement instanceof JsonObject && ((JsonObject) jsonElement).has(getFunctionName()) ) { + jsonElement = ((JsonObject) jsonElement).get(getFunctionName()); + } + return super.refineJsonResponse(jsonElement); + } + }; + } + + < + FluentHelperT extends FluentHelperFunction, ObjectT> + FluentHelperFunction + functionSinglePost( + @Nonnull final Map parameters, + @Nonnull final String functionName, + @Nonnull final Class objectClass ) + { + return function(parameters, functionName, objectClass, HttpPost::new, FluentHelperFunction::executeSingle); + } + + < + FluentHelperT extends FluentHelperFunction, ObjectT> + FluentHelperFunction + functionSingleGet( + @Nonnull final Map parameters, + @Nonnull final String functionName, + @Nonnull final Class objectClass ) + { + return function(parameters, functionName, objectClass, HttpGet::new, FluentHelperFunction::executeSingle); + } + + < + FluentHelperT extends FluentHelperFunction>, ObjectT> + FluentHelperFunction> + functionMultiplePost( + @Nonnull final Map parameters, + @Nonnull final String functionName, + @Nonnull final Class objectClass ) + { + return function(parameters, functionName, objectClass, HttpPost::new, FluentHelperFunction::executeMultiple); + } + + < + FluentHelperT extends FluentHelperFunction>, ObjectT> + FluentHelperFunction> + functionMultipleGet( + @Nonnull final Map parameters, + @Nonnull final String functionName, + @Nonnull final Class objectClass ) + { + return function(parameters, functionName, objectClass, HttpGet::new, FluentHelperFunction::executeMultiple); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java new file mode 100644 index 0000000000..b9accd0332 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java @@ -0,0 +1,216 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; + +import com.google.gson.JsonElement; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestAction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.Lazy; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of any OData function import as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the object this OData request operates on, if any. + * @param + * The type of the result entity, if any. + */ +@Slf4j +public abstract class FluentHelperFunction + extends + FluentHelperBasic +{ + /** + * Upper-case HTTP method derived from {@link #createRequest(URI)} method. + */ + @SuppressWarnings( "this-escape" ) + private final Lazy httpMethod = Lazy.of(() -> createRequest(URI.create("")).getMethod().toUpperCase()); + + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + */ + public FluentHelperFunction( @Nonnull final String servicePath ) + { + super(servicePath, null); + } + + /** + * Getter for the map of parameters to be used in the function call. + *

+ * The map maps the ODataName of a parameter to the corresponding unserialized value. + *

+ * Only literal values are allowed as value. No complex or entity objects. + * + * @return A map containing the parameter for the function call. + */ + @Nonnull + protected abstract Map getParameters(); + + /** + * The exact name of the function to be called on the OData Endpoint. + * + * @return The function name on the endpoint. + */ + @Nonnull + protected abstract String getFunctionName(); + + /** + * Creates a request for this function based on the given {@code URI}. + *

+ * Examples for such requests are {@code HttpGet} and {@code HttpPost}. + * + * @param uri + * The {@code URI} the request should target. + * @return The instantiated request. + */ + @Nonnull + protected abstract HttpUriRequest createRequest( @Nonnull final URI uri ); + + /** + * {@inheritDoc} + *

+ * The function import arguments are encoded as HTTP query expressions. + */ + @Nonnull + @Override + public ODataRequestGeneric toRequest() + { + final ODataRequestGeneric functionImportRequest = instantiateRequest(); + return super.addHeadersAndCustomParameters(functionImportRequest); + } + + @Nonnull + private ODataRequestGeneric instantiateRequest() + { + final String servicePath = getServicePath(); + final ODataResourcePath resourcePath = ODataResourcePath.of(getFunctionName()); + + // get and encode function import parameters as query expressions + final String encodedQuery = ODataFunctionParameters.of(getParameters(), ODataProtocol.V2).toEncodedString(); + + switch( httpMethod.get() ) { + case HttpGet.METHOD_NAME: + return new ODataRequestFunction(servicePath, resourcePath, encodedQuery, ODataProtocol.V2); + case HttpPost.METHOD_NAME: + return new ODataRequestAction(servicePath, resourcePath, null, encodedQuery, ODataProtocol.V2); + default: + throw new IllegalStateException("Unexpected HTTP request method for function import: " + httpMethod); + } + } + + /** + * Default implementation for the case that this function returns a single type. + *

+ * This method can be used in subclasses to implement the {@link #executeRequest(Destination)} method. + * + * @param destination + * The destination to run the function against. + * + * @return The single object returned by the function call. Returns {@code null} if the function did not return a + * result. + * + * @throws ODataException + * If the execution of the function failed. + */ + @SuppressWarnings( "checkstyle:IllegalCatch" ) + @Nullable + protected ObjectT executeSingle( @Nonnull final Destination destination ) + throws ODataException + { + final ODataRequestResultGeneric result = executeInternal(destination); + final Class resultType = getEntityClass(); + + if( resultType.equals(Void.class) ) { + return null; + } + + final ObjectT resultObject = result.as(resultType, this::refineJsonResponse); + + if( resultObject instanceof VdmEntity ) { + final VdmEntity entity = (VdmEntity) resultObject; + + // use version identifier from header if present + result.getVersionIdentifierFromHeader().peek(entity::setVersionIdentifier); + + entity.attachToService(getServicePath(), destination); + } + return resultObject; + } + + /** + * Default implementation for the case that this function returns a collection of entries. + *

+ * This method can be used in subclasses to implement the {@link #executeRequest(Destination)} method. + * + * @param destination + * The destination to run the function against. + * + * @return A list of the objects returned by the function call. Returns an empty list if the function did not return + * a result. + * + * @throws ODataException + * If the execution of the function failed. + */ + @Nonnull + @SuppressWarnings( "unchecked" ) + protected List executeMultiple( @Nonnull final Destination destination ) + throws ODataException + { + final ODataRequestResultGeneric result = executeInternal(destination); + + final List resultObjectList = (List) result.asList(getEntityClass()); + + if( !resultObjectList.isEmpty() && resultObjectList.get(0) instanceof VdmEntity ) { + resultObjectList.forEach(entity -> ((VdmEntity) entity).attachToService(getServicePath(), destination)); + } + return resultObjectList; + } + + @Nonnull + private ODataRequestResultGeneric executeInternal( final Destination destination ) + throws ODataException + { + final ODataRequestGeneric functionImportRequest = toRequest(); + return (ODataRequestResultGeneric) functionImportRequest + .execute(ApacheHttpClient5Accessor.getHttpClient(destination)); + } + + /** + * Transform the JSON element from the response to extract a result entity. By default this method returns the + * original object. + * + * @param jsonElement + * The optional response JSON element + * @return The refined JSON element + */ + @Nullable + protected JsonElement refineJsonResponse( @Nullable final JsonElement jsonElement ) + { + return jsonElement; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java new file mode 100644 index 0000000000..fb7d7f17d8 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java @@ -0,0 +1,31 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Representation of an OData modification request (Create, Update, Delete) as a fluent interface for further + * configuring the request and {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the entity this OData request operates on, if any. + */ +public abstract class FluentHelperModification> + extends + FluentHelperBasic> +{ + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperModification( @Nonnull final String servicePath, @Nullable final String entityCollection ) + { + super(servicePath, entityCollection); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java new file mode 100644 index 0000000000..25088a673b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java @@ -0,0 +1,283 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Streams; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCount; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; + +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData query as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the result entity. + * @param + * The type of the {@link EntityField class that represents fields of the entity}. + */ +@Slf4j +public abstract class FluentHelperRead, SelectableT> + extends + FluentHelperBasic> +{ + + private final StructuredQuery delegateQuery; + + /** + * Instantiates this fluent helper using the given service path and entity collection to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperRead( @Nonnull final String servicePath, @Nonnull final String entityCollection ) + { + super(servicePath, entityCollection); + + delegateQuery = StructuredQuery.onEntity(entityCollection, ODataProtocol.V2); + } + + @Override + @Nonnull + public ODataRequestRead toRequest() + { + final String queryString = delegateQuery.getEncodedQueryString(); + final ODataRequestRead request = + new ODataRequestRead(getServicePath(), entityCollection, queryString, ODataProtocol.V2); + return super.addHeadersAndCustomParameters(request); + } + + @Override + @Nonnull + public FluentHelperT withQueryParameter( @Nonnull final String key, @Nullable final String value ) + { + return super.withQueryParameter(key, value); + } + + /** + * Query modifier to limit which field values of the entity get fetched and populated, and to specify which + * navigation properties to expand. If this method is never called, then all fields will be fetched and populated, + * and no navigation properties expanded. But if this method is called at least once, then only the specified fields + * will be fetched and populated. Calling this multiple times will combine the set(s) of fields and expansions of + * each call. + * + * @param fields + * Fields to select and/or navigation properties to expand. + * @return The same fluent helper with this query modifier applied. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + public FluentHelperT select( @Nonnull final SelectableT... fields ) + { + final Iterable> selectableFields = (Iterable>) Arrays.asList(fields); + return super.select(selectableFields, delegateQuery::select, delegateQuery::select); + } + + /** + * Query modifier to sort the set of returned entities by one or more fields. If this method is called more than + * once, then the result entity set will be sorted by each field in the order that the methods were called. + * + * @param field + * Field to sort by. + * @param order + * Sorting direction to use (ascending or descending). + * + * @return The same fluent helper with this query modifier applied. + */ + @Nonnull + public FluentHelperT orderBy( @Nonnull final EntityField field, @Nonnull final Order order ) + { + final com.sap.cloud.sdk.datamodel.odata.client.query.Order clientTypeOrder = + com.sap.cloud.sdk.datamodel.odata.client.query.Order.valueOf(order.toString()); + delegateQuery.orderBy(field.getFieldName(), clientTypeOrder); + + return getThis(); + } + + /** + * Query modifier to limit the number of entities returned. If this method is never called, then the result entity + * list will not be limited in size. If this method is called multiple times, then only the value of the last call + * will be used. + * + * @param top + * Number of entities to limit the result set to. + * @return The same fluent helper with this query modifier applied. + */ + @Nonnull + public FluentHelperT top( @Nonnull final Number top ) + { + delegateQuery.top(top); + return getThis(); + } + + /** + * Query modifier to not return the first N entities of the result set. If this method is never called, then the + * full list will be returned from the first entity. If this method is called multiple times, then only the value of + * the last call will be used. + * + * @param skip + * Number of entities to skip by. + * @return The same fluent helper with this query modifier applied. + */ + @Nonnull + public FluentHelperT skip( @Nonnull final Number skip ) + { + delegateQuery.skip(skip); + return getThis(); + } + + @Override + @Nonnull + public List executeRequest( @Nonnull final Destination destination ) + throws com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException + { + final Iterable> iterablePages = iteratingPages().executeRequest(destination); + final Iterable iterableItems = Iterables.concat(Objects.requireNonNull(iterablePages)); + return Lists.newArrayList(iterableItems); + } + + /** + * Manually explore the individual pages from the lazy-loading entity result-set. The returning object allows for + * performant consumption of all data through server-driven pagination. + * + * @return An instance of {@link FluentHelperExecutable} that allows for lazy iteration through the result-set + * pages. + */ + @Nonnull + public FluentHelperExecutable>> iteratingPages() + { + return this::executeInternal; + } + + /** + * Manually explore the individual entities from the lazy-loading entity result-set. The returning iterable allows + * for performant consumption of all data through server-driven pagination. + * + * @return An instance of {@link FluentHelperExecutable} that allows for lazy iteration through the result-set + * pages. + */ + @Nonnull + public FluentHelperExecutable> iteratingEntities() + { + // concat applies lazy evaluation so individual pages will still be loaded lazily + return destination -> Iterables.concat(executeInternal(destination)); + } + + /** + * Manually explore the individual entities from the lazy-loading entity result-set. The returning Stream allows for + * performant consumption of all data through server-driven pagination. + * + * @return An instance of {@link FluentHelperExecutable} that allows for lazy iteration through the result-set + * pages. + */ + @Nonnull + public FluentHelperExecutable> streamingEntities() + { + // concat applies lazy evaluation so individual pages will still be loaded lazily + return destination -> Streams.stream(Iterables.concat(executeInternal(destination))); + } + + @Nonnull + private Iterable> executeInternal( @Nonnull final Destination destination ) + throws com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final Iterable> result = toRequest().execute(httpClient).iteratePages(getEntityClass()); + + // Refine lazy iterable to attach destination properties to individual entities in the page lists. + //noinspection StaticPseudoFunctionalStyleMethod,ConstantConditions + return Iterables + .transform( + result, + list -> list + .stream() + .peek(entity -> entity.attachToService(getServicePath(), destination)) // enable lazy loading for navigation properties on entity + .collect(Collectors.toList())); + } + + /** + * Query modifier to restrict the set of returned entities based on the values of one or more fields. If this method + * is never called, then all accessible entities will be fetched. Calling this multiple times will combine the + * filters into one that is the intersection of all filters (filter1 AND filter2 AND ... filterN). + * + * @param expression + * Fluent helper that represents a field value expression. To create this, start with an + * {@link com.sap.cloud.sdk.datamodel.odata.helper.EntityField EntityField} constant in your respective + * entity type for the desired entity field. Then call one of the comparison operators and provide a + * comparison value. Optionally the resulting fluent helper can be chained with other expression fluent + * helpers. + * + * @return The same fluent helper with this query modifier applied. + */ + @Nonnull + public FluentHelperT filter( @Nonnull final ExpressionFluentHelper expression ) + { + delegateQuery.filter(expression.getDelegateExpressionWithoutOuterParentheses()); + return getThis(); + } + + /** + * Query modifier to return only the number of tuples that match the criteria specified in the query. The actual + * tuples are not returned. May be used with all other query modifiers. Any call to top is overloaded with top(0). + * Any call to skip is ignored. + * + * @return The number of tuples that match the criteria specified in the query. + */ + @Nonnull + public FluentHelperCount count() + { + final StructuredQuery prunedQuery = StructuredQuery.onEntity(entityCollection, ODataProtocol.V2); + + delegateQuery.getFilters().forEach(prunedQuery::filter); + delegateQuery.getCustomParameters().forEach(prunedQuery::withCustomParameter); + + final ODataRequestCount requestCount = + new ODataRequestCount( + getServicePath(), + entityCollection, + prunedQuery.getEncodedQueryString(), + ODataProtocol.V2); + + final ODataRequestCount requestCountUpdated = addHeadersAndCustomParameters(requestCount); + + return new FluentHelperCount(requestCountUpdated); + } + + /** + * Set the preferred page size of the OData response. A result-set may be split into multiple pages, each including + * a subset of the entities matching the query. + *

+ * Note: The OData service might ignore the preferred page size setting and may not use pagination + * at all. + * + * @param size + * The preferred page size + * @return This request object with the added parameter. + */ + @Nonnull + public FluentHelperT withPreferredPageSize( final int size ) + { + return withHeader("Prefer", "odata.maxpagesize=" + size); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.java new file mode 100644 index 0000000000..299a47dd3e --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.java @@ -0,0 +1,293 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.google.common.annotations.Beta; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataSerializationException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.request.ETagSubmissionStrategy; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odata.client.request.UpdateStrategy; + +import io.vavr.control.Option; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData update request as a fluent interface for further configuring the request and + * {@link #executeRequest(Destination) executing} it. + * + * @param + * The fluent helper type. + * @param + * The type of the entity to update. + */ +@Slf4j +public abstract class FluentHelperUpdate> + extends + FluentHelperModification +{ + private final Collection> includedFields = new HashSet<>(); + private final Collection> excludedFields = new HashSet<>(); + private UpdateStrategy updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH; + private ETagSubmissionStrategy eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ETAG_FROM_ENTITY; + + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityCollection + * The entity collection to direct the requests to. + */ + public FluentHelperUpdate( @Nonnull final String servicePath, @Nonnull final String entityCollection ) + { + super(servicePath, entityCollection); + } + + /** + * The entity object to be updated by calling the {@link #executeRequest(Destination)} method. + * + * @return The entity to be updated. + */ + protected abstract EntityT getEntity(); + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) getEntity().getClass(); + } + + /** + * The update request will ignore any version identifier present on the entity and not send an `If-Match` header. + *

+ * Warning: This might lead to a response from the remote system that the `If-Match` header is missing. + *

+ * It depends on the implementation of the remote system whether the `If-Match` header is expected. + * + * @return The same request builder that will not send the `If-Match` header in the update request + */ + @Nonnull + public FluentHelperT disableVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_NO_ETAG; + return getThis(); + } + + /** + * The update request will ignore any version identifier present on the entity and update the entity, regardless of + * any changes on the remote entity. + *

+ * Warning: Be careful with this option, as this might overwrite any changes made to the remote + * representation of this object. + * + * @return The same request builder that will ignore the version identifier of the entity to update + */ + @Nonnull + public FluentHelperT matchAnyVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ANY_MATCH_ETAG; + return getThis(); + } + + @Override + @Nonnull + public ModificationResponse executeRequest( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric result = toRequest().execute(httpClient); + + return ModificationResponse.of(result, getEntity(), destination); + } + + @Override + @Nonnull + public ODataRequestUpdate toRequest() + { + final EntityT entity = getEntity(); + final String serializedEntity = getSerializedEntity(); + final String versionIdentifier = + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()); + + final ODataRequestUpdate request = + new ODataRequestUpdate( + getServicePath(), + getEntityCollection(), + ODataEntityKey.of(entity.getKey(), ODataProtocol.V2), + serializedEntity, + updateStrategy, + versionIdentifier, + ODataProtocol.V2); + + return super.addHeadersAndCustomParameters(request); + } + + @Nonnull + private String getSerializedEntity() + { + final EntityT entity = getEntity(); + try { + final List fieldsToExcludeUpdate = + excludedFields + .stream() + .map(EntitySelectable::getFieldName) + .map(FieldReference::of) + .collect(Collectors.toList()); + + final List fieldsToIncludeInUpdate = + includedFields + .stream() + .map(EntitySelectable::getFieldName) + .map(FieldReference::of) + .collect(Collectors.toList()); + + switch( updateStrategy ) { + case REPLACE_WITH_PUT: + return ODataEntitySerializer.serializeEntityForUpdatePut(entity, fieldsToExcludeUpdate); + case MODIFY_WITH_PATCH: + 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); + } + } + catch( final Exception e ) { + final String msg = + String + .format( + "Failed to serialize OData Update HTTP request entity for type %s with strategy %s", + getEntityClass().getSimpleName(), + updateStrategy); + + final ODataRequestUpdate request = + new ODataRequestUpdate( + getServicePath(), + entity.getEntityCollection(), + ODataEntityKey.of(entity.getKey(), ODataProtocol.V2), + "", + updateStrategy, + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()), + ODataProtocol.V2); + throw new ODataSerializationException(request, entity, msg, e); + } + } + + /** + * Allows to explicitly specify entity fields that shall be sent in an update request regardless if the values of + * these fields have been changed. This is helpful in case the API requires to send certain fields in any case in an + * update request. + * + * @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 + @SafeVarargs + @SuppressWarnings( "varargs" ) + public final FluentHelperT includingFields( @Nonnull final EntitySelectable... fields ) + { + includedFields.addAll(Arrays.asList(fields)); + return getThis(); + } + + /** + * Allows to explicitly specify entity fields that should not be sent in an update request. This is helpful in case, + * some services require no read only fields to be sent for update requests. These fields are only excluded in a PUT + * request, they are not considered in a PATCH request. + * + * @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 + @SafeVarargs + @SuppressWarnings( "varargs" ) + public final FluentHelperT excludingFields( @Nonnull final EntitySelectable... fields ) + { + excludedFields.addAll(Arrays.asList(fields)); + return getThis(); + } + + @Nonnull + private String getEntityCollection() + { + return Option.of(entityCollection).getOrElse(() -> getEntity().getEntityCollection()); + } + + /** + * Allows to control that the request to update the entity is sent with the HTTP method PUT and its payload contains + * all fields of the entity, regardless which of them have been changed. + * + * @return The same fluent helper which will replace the entity in the remote system + */ + @Nonnull + public final FluentHelperT replacingEntity() + { + updateStrategy = UpdateStrategy.REPLACE_WITH_PUT; + 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. + * + * @return The same fluent helper which will modify the entity in the remote system. + */ + @Nonnull + 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(); + } + +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.java new file mode 100644 index 0000000000..227c5baa8d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.java @@ -0,0 +1,154 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.annotations.Beta; +import com.google.gson.Gson; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for data modification requests. + * + * @param + * The generic entity type. + */ +@EqualsAndHashCode +@ToString +@RequiredArgsConstructor( staticName = "of", onConstructor = @__( { @Beta } ), access = AccessLevel.PUBLIC ) +@Slf4j +public final class ModificationResponse> +{ + private static final Gson GSON = new Gson(); + + @Nullable + private EntityT modifiedEntity; + + @Nonnull + private Option responseEntity = Option.none(); + + @Nonnull + private final ODataRequestResultGeneric result; + + @Nonnull + private final EntityT originalRequestEntity; + + @Nonnull + private final Destination destination; + + /** + * Get an updated version of the entity. If the service responded with an entity it is returned here. If the service + * didn't respond with an entity but send an @{code ETag} header, the entity is updated and returned. Otherwise a + * copy of the original, unmodified entity is returned. + * + * @return The modified entity. + */ + @Nonnull + public synchronized EntityT getModifiedEntity() + { + if( modifiedEntity == null ) { + evaluateResponse(); + } + return modifiedEntity; + } + + /** + * Get the optional response entity parsed from the HTTP content. + * + * @return The parsed entity or none. + */ + @Nonnull + public synchronized Option getResponseEntity() + { + // We synchronize the full method here because Fortify does not like double checked locking: + // https://vulncat.fortify.com/en/detail?id=desc.structural.java.code_correctness_double_checked_locking + if( modifiedEntity == null ) { + evaluateResponse(); + } + return responseEntity; + } + + @SuppressWarnings( "unchecked" ) + private void evaluateResponse() + { + responseEntity = parseEntityFromResponse(); + + // create a copy before modifying the version identifier to not change existing objects + final EntityT entityToModify = responseEntity.getOrElse(originalRequestEntity); + + // clone entity + modifiedEntity = GSON.fromJson(GSON.toJson(entityToModify), (Class) originalRequestEntity.getClass()); + + modifiedEntity.attachToService(result.getODataRequest().getServicePath(), destination); + + modifiedEntity.setVersionIdentifier(getUpdatedVersionIdentifier().getOrNull()); + } + + /** + * Access the original entity used to make the request. + * + * @return The original entity object used to perform an OData request. + */ + @Nonnull + public EntityT getRequestEntity() + { + return originalRequestEntity; + } + + /** + * Get the response status code. + * + * @return The integer representation of the HTTP status code. + */ + public int getResponseStatusCode() + { + return result.getHttpResponse().getCode(); + } + + /** + * Get the response headers. + * + * @return The headers of the HTTP status code. + */ + @Nonnull + public Map> getResponseHeaders() + { + return result.getAllHeaderValues(); + } + + /** + * Get the version identifier present in the response headers, the identifier of the original entity or none, if + * neither exist. + * + * @return An up to date version identifier or none. + */ + @Nonnull + public Option getUpdatedVersionIdentifier() + { + return result + .getVersionIdentifierFromHeader() + .orElse(() -> getResponseEntity().flatMap(entity -> entity.getVersionIdentifier())); + } + + @Nonnull + @SuppressWarnings( "unchecked" ) + private Option parseEntityFromResponse() + { + return Try + .of(() -> result.as((Class) originalRequestEntity.getClass())) + .onFailure(e -> log.debug("Failed to parse entity from HTTP response.", e)) + .toOption() + .peek(entity -> result.getVersionIdentifierFromHeader().peek(entity::setVersionIdentifier)); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.java new file mode 100644 index 0000000000..e934c50324 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.java @@ -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 +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.java new file mode 100644 index 0000000000..3898351d0d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.java @@ -0,0 +1,321 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.StreamSupport; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sap.cloud.sdk.datamodel.odata.adapter.ODataNumberSerializer; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +final class ODataEntitySerializer +{ + private static final Gson GSON; + private static final Gson GSON_SERIALIZING_NULLS; + + static { + final ODataNumberSerializer numberSerializer = new ODataNumberSerializer(); + final GsonBuilder builder = + new GsonBuilder() + .registerTypeAdapter(Long.class, numberSerializer) + .registerTypeAdapter(Double.class, numberSerializer) + .registerTypeAdapter(Float.class, numberSerializer) + .registerTypeAdapter(BigDecimal.class, numberSerializer); + // CXSDK currently serializes Edm.SByte as plain number so the client also does it for consistency. + // .registerTypeAdapter(Byte.class, numberSerializer) + + GSON = builder.create(); + GSON_SERIALIZING_NULLS = builder.serializeNulls().create(); + } + + /** + * Serializes an entity for update request (PUT). Allowing null values. Removing potential "versionIdentifier" + * fields. + * + * @param entity + * The OData V2 entity reference. + * @param excludedFields + * Collection of fields to be excluded in the update (PUT) request. + * @return The serialized JSON string for entity update request. + */ + @Nonnull + static String serializeEntityForUpdatePut( + @Nonnull final VdmEntity entity, + @Nullable final Collection excludedFields ) + { + final JsonObject jsonObject = GSON_SERIALIZING_NULLS.toJsonTree(entity).getAsJsonObject(); + + removeVersionIdentifier(jsonObject); + + // find field names to be removed from PUT request + if( excludedFields != null ) { + excludedFields.stream().map(FieldReference::getFieldName).forEach(jsonObject::remove); + } + + return GSON_SERIALIZING_NULLS.toJson(jsonObject); + } + + /** + * Serializes an entity for create request. Ignoring empty collections and null values. + * + * @param entity + * The OData V2 entity reference. + * @return The serialized JSON string for entity create request. + */ + @Nonnull + static String serializeEntityForCreate( @Nonnull final VdmEntity entity ) + { + // When using builder pattern, all 1:n navigation properties will be initialized with `new ArrayList()` instead of expected `null` + final JsonObject jsonObject = GSON.toJsonTree(entity).getAsJsonObject(); + + removeEmptyArrays(jsonObject); + + return GSON.toJson(jsonObject); + } + + /** + * Serializes an entity for update request (PATCH). Allowing null values. + * + * @param entity + * The OData V2 entity reference. + * @param includedFields + * Collection of fields to be included in the update (PATCH) request. + * @return The serialized JSON string for entity update request. + */ + @Nonnull + static String serializeEntityForUpdatePatchShallow( + @Nonnull final VdmEntity entity, + @Nonnull final Collection includedFields ) + { + final JsonObject fullEntity = GSON_SERIALIZING_NULLS.toJsonTree(entity).getAsJsonObject(); + + // find field names to be patched + final Set fieldNamesToPatch = new HashSet<>(entity.getChangedFields().keySet()); + includedFields.stream().map(FieldReference::getFieldName).forEach(fieldNamesToPatch::add); + log.debug("The following fields are marked for updates: {}.", fieldNamesToPatch); + + final JsonObject partialEntity = new JsonObject(); + + fieldNamesToPatch.forEach(key -> partialEntity.add(key, fullEntity.get(key))); + + return GSON_SERIALIZING_NULLS.toJson(partialEntity); + } + + /** + * Serializes an entity for update request (PATCH) including changes in nested properties. Allowing null values. + * Resulting JSON contains the full value of complex fields for changing any nested field. + * + * @param entity + * The OData V2 entity reference. + * @param includedFields + * Collection of fields to be included in the update (PATCH) request. + * @return The serialized JSON string for entity update request. + */ + @Nonnull + static String serializeEntityForUpdatePatchRecursiveFull( + @Nonnull final VdmEntity entity, + @Nonnull final Collection includedFields ) + { + final JsonObject fullEntityJson = GSON_SERIALIZING_NULLS.toJsonTree(entity).getAsJsonObject(); + final JsonObject patchObject = new JsonObject(); + + final Set changedFieldNames = new HashSet<>(entity.getChangedFields().keySet()); + includedFields.stream().map(FieldReference::getFieldName).forEach(changedFieldNames::add); + changedFieldNames.forEach(key -> patchObject.add(key, fullEntityJson.get(key))); + + entity + .toMapOfFields() + .entrySet() + .stream() + .filter(entry -> !patchObject.has(entry.getKey())) + .filter(entry -> containsNestedChangedFields(entry.getValue())) + .forEach(entry -> patchObject.add(entry.getKey(), fullEntityJson.get(entry.getKey()))); + + log.debug("The following object is serialized for update : {}.", patchObject); + + return GSON_SERIALIZING_NULLS.toJson(patchObject); + } + + /** + * Checks if the given complex object contains any changed fields in its nested fields. + * + * @param obj + * the complex object to check + * @return true if the complex object contains any changed fields, false otherwise + */ + private static boolean containsNestedChangedFields( final Object obj ) + { + if( obj instanceof VdmComplex vdmComplex ) { + if( !vdmComplex.getChangedFields().isEmpty() ) { + return true; + } + for( final Object complexField : vdmComplex.toMapOfFields().values() ) { + if( containsNestedChangedFields(complexField) ) { + return true; + } + } + } + return false; + } + + /** + * Serializes an entity for update request (PATCH) including changes in nested properties. Allowing null values. + * Resulting JSON contains only the changed fields (including nested changes). + * + * @param entity + * The OData V2 entity reference. + * @param includedFields + * Collection of fields to be included in the update (PATCH) request. + * @return The serialized JSON string for entity update request. + */ + @Nonnull + static String serializeEntityForUpdatePatchRecursiveDelta( + @Nonnull final VdmEntity entity, + @Nonnull final Collection includedFields ) + { + final JsonObject fullEntityJson = GSON_SERIALIZING_NULLS.toJsonTree(entity).getAsJsonObject(); + final JsonObject patchObject = new JsonObject(); + + // Recursively build patch object from changed fields + final JsonObject tempPatchObject = createPatchObjectRecursiveDelta(entity, fullEntityJson); + + // Add included fields (from the root only) + includedFields + .stream() + .map(FieldReference::getFieldName) + .forEach(key -> patchObject.add(key, fullEntityJson.get(key))); + + // Merge all fields from the tempPatchObject if not already present + tempPatchObject + .entrySet() + .stream() + .filter(entry -> !patchObject.has(entry.getKey())) + .forEach(entry -> patchObject.add(entry.getKey(), entry.getValue())); + + log.debug("The following delta object is serialized for update : {}.", patchObject); + + return GSON_SERIALIZING_NULLS.toJson(patchObject); + } + + /** + * Recursively builds a patch object for a VdmObject by including only changed fields. Complex fields are traversed + * recursively. + * + * @param vdmObject + * the VdmObject (entity or complex) to build the patch from + * @param jsonObject + * the full JSON representation of this object + * @return a JsonObject that contains only changed fields (including nested changes) + */ + @Nonnull + private static + JsonObject + createPatchObjectRecursiveDelta( @Nonnull final VdmObject vdmObject, @Nonnull final JsonObject jsonObject ) + { + final JsonObject patch = new JsonObject(); + + // Process all complex fields and recursively build patch for the complex field + vdmObject.toMapOfFields().forEach(( fieldName, val ) -> { + if( val instanceof VdmComplex complexField ) { + final var childJsonObject = jsonObject.getAsJsonObject(fieldName); + final var childJsonObjectDelta = createPatchObjectRecursiveDelta(complexField, childJsonObject); + if( !childJsonObjectDelta.isEmpty() ) { + patch.add(fieldName, childJsonObjectDelta); + } + } + }); + + // Add explicitly changed fields + vdmObject.getChangedFields().keySet().forEach(key -> patch.add(key, jsonObject.get(key))); + + return patch; + } + + private static void removeVersionIdentifier( @Nonnull final JsonObject jsonObject ) + { + log.debug("Removing redundant \"versionIdentifier\" recursively from JSON object: {}", jsonObject); + + final Predicate isNullOrString = + obj -> obj.isJsonNull() || obj.isJsonPrimitive() && obj.getAsJsonPrimitive().isString(); + + traverseJsonObject( + jsonObject, + ( element, name ) -> "versionIdentifier".equals(name) && isNullOrString.test(element), + JsonObject::remove); + + log.debug("JSON object after removing redundant \"versionIdentifier\": {}", jsonObject); + } + + private static void removeEmptyArrays( @Nonnull final JsonObject jsonObject ) + { + log.debug("Removing empty arrays recursively from JSON object: {}", jsonObject); + + traverseJsonObject( + jsonObject, + ( element, name ) -> element.isJsonArray() && element.getAsJsonArray().size() == 0, + JsonObject::remove); + + log.debug("JSON object after removing empty arrays: {}", jsonObject); + } + + /** + * Traverse the JSON object tree to apply an action on filtered items. + * + * @param jsonObject + * The current JSON object to check properties for. + * @param filter + * The filter as lambda (predicate) for property value and property key. + * @param action + * The action as lambda (consumer) for parent object and filtered property key. + */ + private static void traverseJsonObject( + @Nonnull final JsonObject jsonObject, + @Nonnull final BiPredicate filter, + @Nonnull final BiConsumer action ) + { + final List filteredChildKeys = new ArrayList<>(); + + jsonObject.entrySet().forEach(entry -> { + if( entry.getValue().isJsonObject() ) { + // Apply this logic recursively for all nested objects + traverseJsonObject(entry.getValue().getAsJsonObject(), filter, action); + } else if( filter.test(entry.getValue(), entry.getKey()) ) { + // Collect key names for children that tested against the provided filter predicate + filteredChildKeys.add(entry.getKey()); + } else if( entry.getValue().isJsonArray() ) { + // Apply this logic recursively for lists of nested objects + final JsonArray jsonArray = entry.getValue().getAsJsonArray(); + StreamSupport + .stream(jsonArray.spliterator(), false) + .filter(JsonElement::isJsonObject) + .map(JsonElement::getAsJsonObject) + .forEach(o -> traverseJsonObject(o, filter, action)); + } + }); + + log + .trace( + "Applying the provided action on parent element {} for the following child items: {}", + jsonObject, + filteredChildKeys); + + filteredChildKeys.forEach(affectedChildKey -> action.accept(jsonObject, affectedChildKey)); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.java new file mode 100644 index 0000000000..bd5f9b329a --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.java @@ -0,0 +1,24 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +/** + * Interface designed to be implemented by {@link EntityLink} class in order to provide a filter function. + * + * @param + * The entity where the link starts. + * @param + * The entity where the link ends (i.e. the related entity). + */ +public interface OneToOneLink +{ + /** + * Add filter to a one-to-one navigation property relationship. + * + * @param filterExpression + * The filter expression to use for resolving the navigation property + * @return The fluent helper to continue constructing a filter expression. + */ + @Nonnull + ExpressionFluentHelper filter( @Nonnull final ExpressionFluentHelper filterExpression ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.java new file mode 100644 index 0000000000..7ecf43a135 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.java @@ -0,0 +1,17 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +/** + * Used with orderBy methods in entity fluent helper objects to set the sorting order of field values. + */ +public enum Order +{ + /** + * Sort field values in ascending order. + */ + ASC, + + /** + * Sort field values in descending order. + */ + DESC +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.java new file mode 100644 index 0000000000..efa3f3c914 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.java @@ -0,0 +1,32 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import javax.annotation.Nonnull; + +/** + * Representation of any OData function import as a fluent class for further configuring the request and + * {@link #executeRequest(Destination) executing} it. This is specifically for functions that return either a single + * primitive value or entity value + * + * @param + * The fluent helper type. + * @param + * The type of the object this OData request operates on, if any. + * @param + * The type of the result entity, if any. + */ +public abstract class SingleValuedFluentHelperFunction + extends + FluentHelperFunction +{ + + /** + * Instantiates this fluent helper using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + */ + public SingleValuedFluentHelperFunction( @Nonnull final String servicePath ) + { + super(servicePath); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.java new file mode 100644 index 0000000000..74297e4257 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.java @@ -0,0 +1,15 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import lombok.EqualsAndHashCode; + +/** + * Complex type in the virtual data model. + * + * @param + * Object type of the complex type. + */ +@EqualsAndHashCode( callSuper = true, doNotUseGetters = true ) +public abstract class VdmComplex extends VdmObject +{ + +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.java new file mode 100644 index 0000000000..837c6ba1b1 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.java @@ -0,0 +1,226 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Iterables; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataRequestException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a {@link VdmObject} which is an entity. Entities may have a version identifier. + * + * @param + * The specific entity data type. + */ +@EqualsAndHashCode( callSuper = true, doNotUseGetters = true ) +public abstract class VdmEntity extends VdmObject +{ + @SerializedName( "versionIdentifier" ) + @JsonProperty( "versionIdentifier" ) + @Nullable + private String versionIdentifier = null; + + /** + * The service path only used for the fetch commands of this entity. + *

+ * Note: Use with caution, as this can easily break the fetch call on this entity. See the interface of the + * corresponding service for the default service path. + */ + @Getter( AccessLevel.PROTECTED ) + @Setter( AccessLevel.PROTECTED ) + @JsonIgnore + @EqualsAndHashCode.Exclude + private transient String servicePathForFetch; + + /** + * Convenience field for reusing the same destination with multiple queries (e.g. fetching associated entities). + */ + @Getter( AccessLevel.PROTECTED ) + @Setter( AccessLevel.PROTECTED ) + @JsonIgnore + @EqualsAndHashCode.Exclude + private transient Destination destinationForFetch; + + /** + * Getter for the version identifier of this entity. + *

+ * This identifier can be used to compare this entity with a remote one. As not the whole entity has to be sent this + * reduces the request overhead. + *

+ * Actual use cases can be checking whether this entity is still current with regards to the remote entity, and + * ensuring that a update/delete operation is done on the expected version of the remote entity. + * + * @return The optional version identifier. + */ + @Nonnull + public Option getVersionIdentifier() + { + return Option.of(versionIdentifier); + } + + /** + * Setter for the version identifier of this entity. + *

+ * This identifier can be used to compare this entity with a remote one. As not the whole entity has to be sent this + * reduces the request overhead. + *

+ * Actual use cases can be checking whether this entity is still current with regards to the remote entity, and + * ensuring that a update/delete operation is done on the expected version of the remote entity. + * + * @param versionIdentifier + * The version identifier of this entity. + */ + public void setVersionIdentifier( @Nullable final String versionIdentifier ) + { + this.versionIdentifier = versionIdentifier; + } + + /** + * Used by fluent helpers and navigation property methods to construct OData queries. + * + * @return EDMX name of the entity collection identifier. + */ + @Nonnull + protected abstract String getEntityCollection(); + + /** + * Used by fluent helpers and navigation property methods to construct OData queries. + * + * @return Default context path to the OData service. In other words, everything in between the + * {@code protocol://hostname:port} and the OData resource name (entity set, {@code $metadata}, etc.) + */ + @Nullable + protected String getDefaultServicePath() + { + return null; + } + + /** + * Sets the service path and destination for the fetch commands of this entity. + *

+ * Also applies to any associated entities (navigation properties) that were previously fetched. + *

+ * Note: Use with caution, as this can easily break the fetch calls on this entity. See the interface of the + * corresponding service for the default service path. + * + * @param servicePath + * Optional parameter. New service path to apply to this entity and any associated entities that were + * previously fetched. If a null value is provided and the service path has never been set, then the + * service path will be set to the default defined in the corresponding service interface. + * @param destination + * New destination to apply to this entity and any associated entities that were previously fetched. + */ + protected void attachToService( @Nullable final String servicePath, @Nonnull final Destination destination ) + { + if( servicePath != null ) { + servicePathForFetch = servicePath; + } else if( servicePathForFetch == null ) { + servicePathForFetch = getDefaultServicePath(); + } + + destinationForFetch = destination; + + toMapOfNavigationProperties().values().forEach(navProperty -> { + if( navProperty instanceof Iterable ) { + final Iterable navPropertyList = (Iterable) navProperty; + final boolean itemTypeIsEntity = Iterables.getFirst(navPropertyList, null) instanceof VdmEntity; + + if( itemTypeIsEntity ) { + for( final Object childEntity : navPropertyList ) { + final VdmEntity vdmEntity = (VdmEntity) childEntity; + vdmEntity.attachToService(servicePathForFetch, destinationForFetch); + } + } + } else if( navProperty instanceof VdmEntity ) { + final VdmEntity vdmEntity = (VdmEntity) navProperty; + vdmEntity.attachToService(servicePathForFetch, destinationForFetch); + } + }); + } + + /** + * Helper method to lazily resolve a field value from current entity. + * + * @param fieldName + * The field name to lookup. + * @param fieldType + * The field type to cast the value to. + * @param + * The generic type parameter. + * @return A list of requested values. + */ + @Nonnull + protected > List fetchFieldAsList( + @Nonnull final String fieldName, + @Nonnull final Class fieldType ) + { + final Destination destination = getDestinationForFetch(); + final ODataRequestResultGeneric response = fetchField(fieldName, destination); + final List entityList = response.asList(fieldType); + for( final T entity : entityList ) { + entity.attachToService(getServicePathForFetch(), destination); + } + return entityList; + } + + /** + * Helper method to lazily resolve a field value from current entity. + * + * @param fieldName + * The field name to lookup. + * @param fieldType + * The field type to cast the value to. + * @param + * The generic type parameter. + * @return The requested values. + */ + @Nonnull + protected > T fetchFieldAsSingle( + @Nonnull final String fieldName, + @Nonnull final Class fieldType ) + { + final Destination destination = getDestinationForFetch(); + final ODataRequestResultGeneric response = fetchField(fieldName, destination); + final T entity = response.as(fieldType); + entity.attachToService(getServicePathForFetch(), destination); + return entity; + } + + @Nonnull + private ODataRequestResultGeneric fetchField( final String fieldName, final Destination destination ) + { + final ODataEntityKey entityKey = ODataEntityKey.of(getKey(), ODataProtocol.V2); + final ODataResourcePath path = ODataResourcePath.of(getEntityCollection(), entityKey).addSegment(fieldName); + final ODataRequestRead request = new ODataRequestRead(getServicePathForFetch(), path, null, ODataProtocol.V2); + if( destination == null ) { + throw new ODataRequestException( + request, + "Failed to fetch related objects from field name " + + fieldName + + ": The entity was created locally without an assigned HttpDestination. This method is applicable only on entities which were retrieved or created using the OData VDM.", + null); + } + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + return request.execute(httpClient); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.java new file mode 100644 index 0000000000..08d6c65692 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.java @@ -0,0 +1,54 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.lang.reflect.InvocationTargetException; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException; + +import lombok.RequiredArgsConstructor; + +/** + * Utility class to manage OData entity deserialization. + * + * @param + * Entity type to create new instances from. + */ +@RequiredArgsConstructor +public final class VdmEntityUtil> +{ + private final Class entityClass; + + @Nonnull + EntityT newInstance() + { + try { + return entityClass.getDeclaredConstructor().newInstance(); + } + catch( final + InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e ) { + throw new ShouldNotHappenException( + "Failed to instantiate object of type " + entityClass.getSimpleName(), + e); + } + } + + /** + * Helper method to resolve the expected entity type for the provided fluent helper instance. For internal use. + * + * @param fluentHelper + * The fluent helper instance to resolve the entity type for. + * @param + * The generic entity type. + * @return The entity type. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + public static Class getEntityClass( @Nonnull final FluentHelperBasic fluentHelper ) + { + return (Class) fluentHelper.getEntityClass(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java new file mode 100644 index 0000000000..479a87760c --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java @@ -0,0 +1,97 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.HttpEntity; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataRequestException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataResponseException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.EqualsAndHashCode; + +/** + * Represents a media entity which exposes additional data under a {@code $value} endpoint. + * + * @param + * The specific entity type. + */ +@EqualsAndHashCode( callSuper = true, doNotUseGetters = true ) +public abstract class VdmMediaEntity extends VdmEntity +{ + /** + * Get the binary data stream (file) from this media entity. Perform this operation after retrieving the + * entity object from the OData service. + *

+ * Alternatively, you can use this method to only retrieve the media resource without requesting the entity + * data. Build this entity via its {@code .builder()} and use {@link #attachToService(String, Destination)} to + * declare a service path and destination to request the media resource from. You can obtain the service path from + * the {@code #DEFAULT_SERVICE_PATH}, e.g. + * {@code BusinessPartnerServiceBusinessPartnerService.DEFAULT_SERVICE_PATH} + *

+ * Please ensure this stream is closed after usage. The below example achieves this using + * try-with-resources: + * + *

+     * try( InputStream content = entity.fetchMediaStream() ) {
+     *     // do something with the content here
+     * }
+     * 
+ * + * @return File content as an {@link InputStream}. + * @throws ODataException + * if the request could not be sent or the OData service responded with an error. + */ + @Nonnull + public InputStream fetchMediaStream() + throws ODataException + { + final ODataResourcePath resource = + ODataResourcePath + .of(getEntityCollection(), ODataEntityKey.of(getKey(), ODataProtocol.V2)) + .addSegment("$value"); + + final ODataRequestReadByKey request = + new ODataRequestReadByKey(getServicePathForFetch(), resource, null, ODataProtocol.V2); + + final Destination destination = getDestinationForFetch(); + if( destination == null ) { + throw new ODataRequestException( + request, + "Failed to fetch media stream.", + new IllegalStateException( + "Unable to execute OData query. The entity was created locally without an assigned HttpDestination. This method is applicable only on entities which were retrieved or created using the OData VDM.")); + } + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric result = request.execute(httpClient); + final HttpEntity entity = result.getHttpResponse().getEntity(); + if( entity == null ) { + throw new ODataResponseException( + request, + result.getHttpResponse(), + "Failed to read the input stream of the OData response: Response didn't contain any payload.", + null); + } + try { + return entity.getContent(); + } + catch( final IOException | UnsupportedOperationException e ) { + throw new ODataResponseException( + request, + result.getHttpResponse(), + "Failed to read the input stream of the OData response.", + e); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.java new file mode 100644 index 0000000000..48549f0e40 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.java @@ -0,0 +1,373 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.sap.cloud.sdk.s4hana.datamodel.odata.exception.NoSuchEntityFieldException; +import com.sap.cloud.sdk.typeconverter.TypeConverter; + +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * Superclass of all entities which contains common elements such as a generic representation of custom fields. + * + * @param + * The type of the implementing object. + */ +@ToString( doNotUseGetters = true ) +@EqualsAndHashCode( doNotUseGetters = true ) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE ) +public abstract class VdmObject +{ + @JsonIgnore + private final transient Map customFields = new LinkedHashMap<>(); + + /** + * A mapping of the OData field name to the original value. + *

+ * This should be updated via {@link #rememberChangedField(String, Object)} on every set call of a property. + */ + @JsonIgnore + @Nonnull + protected final transient Map changedOriginalFields = new HashMap<>(); + + /** + * Returns the names of all custom fields of this object. + * + * @return The names of the custom fields of this object. + */ + @Nonnull + public Set getCustomFieldNames() + { + return customFields.keySet(); + } + + /** + * Returns all custom field names and values of this object. + * + * @return All names & values of custom fields as a map. + */ + @JsonAnyGetter + @Nonnull + public Map getCustomFields() + { + return customFields; + } + + /** + * Sets the value of a single custom field. + * + * @param customFieldName + * Name of the custom field. + * @param value + * Value of the custom field. + */ + @JsonAnySetter + public void setCustomField( @Nonnull final String customFieldName, @Nullable final Object value ) + { + rememberChangedField(customFieldName, customFields.get(customFieldName)); + customFields.put(customFieldName, value); + } + + /** + * Sets the value of a single custom field. If the EntityField passed as parameter holds a TypeConverter, the value + * will be converted before it's stored. + * + * @param customField + * Name of the custom field, represented as an EntityField object. + * @param value + * Value of the custom field. + * @param + * The type of the custom field to set. + */ + public void setCustomField( + @Nonnull final EntityField customField, + @Nullable final FieldT value ) + { + if( customField.getTypeConverter() != null ) { + setCustomField(customField.getFieldName(), customField.getTypeConverter().toDomain(value).get()); + } else { + setCustomField(customField.getFieldName(), value); + } + } + + /** + * Checks whether this object contains a custom field with the given name. + * + * @param customFieldName + * Name of the custom field to check for + * + * @return {@code true} if this entity has a custom field with the given name, {@code false} otherwise. + */ + public boolean hasCustomField( @Nonnull final String customFieldName ) + { + return customFields.containsKey(customFieldName); + } + + /** + * Checks whether this object contains a value for the given custom field. + * + * @param customField + * Custom field to check for, represented as an {@code EntityField} object. + * + * @return {@code true} if this object has a custom field with the name of the given field, {@code false} otherwise. + */ + public boolean hasCustomField( @Nonnull final EntityField customField ) + { + return hasCustomField(customField.getFieldName()); + } + + /** + * This method allows for retrieval of custom fields that are added to the underlying OData services. + * + * @param customFieldName + * Name of the field returned by the underlying OData service. + * @param + * The type of the returned field. + * + * @return The value of the custom field. Actual type will depend on the type configured in the underlying OData + * service. + * + * @throws NoSuchEntityFieldException + * if no field with the given name could be found. + */ + @SuppressWarnings( "unchecked" ) + @Nullable + public FieldT getCustomField( @Nonnull final String customFieldName ) + throws NoSuchEntityFieldException + { + if( !hasCustomField(customFieldName) ) { + throw new NoSuchEntityFieldException("Object has no field with name '" + customFieldName + "'."); + } + return (FieldT) customFields.get(customFieldName); + } + + @SuppressWarnings( "unchecked" ) + @Nullable + private FieldT getCustomField( + @Nonnull final String customFieldName, + @Nonnull final TypeConverter typeConverter ) + throws NoSuchEntityFieldException + { + if( !hasCustomField(customFieldName) ) { + throw new NoSuchEntityFieldException("Object has no field with name '" + customFieldName + "'."); + } + return typeConverter.fromDomain((T) customFields.get(customFieldName)).get(); + } + + /** + * This method allows for retrieval of custom fields that are added to the underlying OData services. If the + * EntityField passed as parameter holds a TypeConverter, the value will be converted before it's returned. + * + * @param customField + * Field returned by the underlying OData service. + * @param + * The type of the returned field. + * + * @return The value of the custom field. Actual type will depend on the type configured in the underlying OData + * service. + * + * @throws NoSuchEntityFieldException + * if no field with the given name could be found. + */ + @Nullable + public FieldT getCustomField( @Nonnull final EntityField customField ) + throws NoSuchEntityFieldException + { + if( customField.getTypeConverter() != null ) { + return getCustomField(customField.getFieldName(), customField.getTypeConverter()); + } else { + return getCustomField(customField.getFieldName()); + } + } + + /** + * Returns the class of this object. + * + * @return The class of this object. + */ + @Nonnull + public abstract Class getType(); + + /** + * Returns the compound key of this object. + * + * @return The compound key of this object. + */ + @Nonnull + protected Map getKey() + { + return new HashMap<>(); + } + + /** + * Sets the values of all custom fields contained in the given {@code values}. + *

+ * Afterwards, marks all fields as unchanged. + *

+ * + * @param values + * The map of custom fields to set. + */ + protected void fromMap( final Map values ) + { + for( final Map.Entry entry : values.entrySet() ) { + setCustomField(entry.getKey(), entry.getValue()); + } + + resetChangedFields(); + } + + /** + * Returns a map of all custom fields contained in this object. + * + * @return A map of all custom fields contained in this object. + */ + @Nonnull + protected Map toMapOfCustomFields() + { + return Maps.newHashMap(getCustomFields()); + } + + /** + * Returns a set of all custom field names contained in this object. + * + * @return A set of all custom field names contained in this object. + */ + @Nonnull + protected Set getSetOfCustomFields() + { + return Sets.newHashSet(getCustomFields().keySet()); + } + + /** + * Returns a map of all fields contained in this object. + * + * @return A map of all fields contained in this object. + */ + @Nonnull + protected Map toMapOfFields() + { + return new HashMap<>(); + } + + /** + * Returns a set of all field names contained in this object. + * + * @return A set of all field names contained in this object. + */ + @Nonnull + protected Set getSetOfFields() + { + return Sets.newHashSet(toMapOfFields().keySet()); + } + + /** + * Returns a map of all navigation properties contained in this object. + * + * @return A map of all navigation properties contained in this object. + */ + @Nonnull + protected Map toMapOfNavigationProperties() + { + return new HashMap<>(); + } + + /** + * Returns a set of all navigation property names contained in this object. + * + * @return A set of all navigation property names contained in this object. + */ + @Nonnull + protected Set getSetOfNavigationProperties() + { + return Sets.newHashSet(toMapOfNavigationProperties().keySet()); + } + + /** + * Returns a map of all fields, navigation properties, and custom fields contained in this object. + * + * @return A map of all fields, navigation properties, and custom fields contained in this object. + */ + @Nonnull + protected Map toMap() + { + final Map values = new HashMap<>(); + + values.putAll(toMapOfFields()); + values.putAll(toMapOfNavigationProperties()); + values.putAll(toMapOfCustomFields()); + + return values; + } + + /** + * Returns map of all fields which have been changed on this entity along with their updated values. + * + * @return Map containing all changed fields with their current value. + */ + @Nonnull + public Map getChangedFields() + { + final Map changedFields = new HashMap<>(); + + final Map currentFields = toMapOfFields(); + currentFields.putAll(getCustomFields()); + + for( final Map.Entry changedOriginalField : changedOriginalFields.entrySet() ) { + final Object originalValue = changedOriginalField.getValue(); + final Object currentValue = currentFields.get(changedOriginalField.getKey()); + + if( originalValue != null && !originalValue.equals(currentValue) + || originalValue == null && currentValue != null ) { + + changedFields.put(changedOriginalField.getKey(), currentValue); + } + } + + return changedFields; + } + + /** + * Remembers the original value of a changed field. + * + * @param fieldName + * The name of the field that is changed. + * @param valueBeforeChange + * The original value before the change. + */ + protected void rememberChangedField( @Nonnull final String fieldName, @Nullable final Object valueBeforeChange ) + { + if( !changedOriginalFields.containsKey(fieldName) ) { + changedOriginalFields.put(fieldName, valueBeforeChange); + } + } + + /** + * Resets the map of all fields which have been changed on this entity. + *

+ * After calling this method, no field is considered changed, until you change the value of fields on this entity + * afterwards. + */ + public void resetChangedFields() + { + changedOriginalFields.clear(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.java new file mode 100644 index 0000000000..d19638cc57 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.java @@ -0,0 +1,216 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestAction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestDelete; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperCreate; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperDelete; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperUpdate; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; + +import lombok.RequiredArgsConstructor; + +/** + * Representation of any changeset in a OData batch request as a fluent interface. + * + * @param + * The fluent helper type of the associated OData batch request. + * @param + * The current type of the implementing fluent helper instance. + */ +@RequiredArgsConstructor +public abstract class BatchChangeSetFluentHelperBasic, ThisT> + implements + FluentHelperBatchEndChangeSet +{ + private final FluentHelperBatchT typedBatchFluentHelper; + private final BatchFluentHelperBasic basicBatchFluentHelper; + + private final List operations = new ArrayList<>(); + + /** + * Method to safely return the current fluent helper instance upon public method calls. + * + * @return The current fluent helper instance. + */ + @Nonnull + protected abstract ThisT getThis(); + + /** + * Add a delete operation to the current changeset request. + * + * @param serviceMethod + * The method of a delete fluent helper from the given service class. + * @param entity + * The entity instance to be deleted. + * @param + * The entity type to guarantee type safety. + * @return The current fluent helper instance. + */ + @Nonnull + protected > ThisT addRequestDelete( + final Function> serviceMethod, + final EntityT entity ) + { + final FluentHelperDelete delete = serviceMethod.apply(entity); + return addRequest(delete); + } + + /** + * Adds a delete operation to the current changeset request. + * + * @param deleteRequest + * The {@link FluentHelperDelete} that represents the delete operation. + * @return The current fluent helper instance. + */ + @Nonnull + protected ThisT addRequest( @Nonnull final FluentHelperDelete deleteRequest ) + { + final ODataRequestDelete request = deleteRequest.toRequest(); + basicBatchFluentHelper.requestMapping.put(deleteRequest, request); + + final BatchRequestChangeSetOperation operation = + new BatchRequestChangeSetOperation(( changeSet ) -> changeSet.addDelete(request), deleteRequest); + + operations.add(operation); + return getThis(); + } + + /** + * Add a create operation to the current changeset request. + * + * @param serviceMethod + * The method of a create fluent helper from the given service class. + * @param entity + * The entity instance to be created. + * @param + * The entity type to guarantee type safety. + * @return The current fluent helper instance. + */ + @Nonnull + protected > ThisT addRequestCreate( + final Function> serviceMethod, + final EntityT entity ) + { + final FluentHelperCreate create = serviceMethod.apply(entity); + return addRequest(create); + } + + /** + * Adds a create operation to the current changeset request. + * + * @param createRequest + * The {@link FluentHelperCreate} that represents the create operation. + * @return The current fluent helper instance. + */ + @Nonnull + protected ThisT addRequest( @Nonnull final FluentHelperCreate createRequest ) + { + final ODataRequestCreate request = createRequest.toRequest(); + basicBatchFluentHelper.requestMapping.put(createRequest, request); + + final BatchRequestChangeSetOperation operation = + new BatchRequestChangeSetOperation(( changeSet ) -> changeSet.addCreate(request), createRequest); + + operations.add(operation); + return getThis(); + } + + /** + * Add an update operation to the current changeset request. + * + * @param serviceMethod + * The method of an update fluent helper from the given service class. + * @param entity + * The entity instance to be updated. + * @param + * The entity type to guarantee type safety. + * @return The current fluent helper instance. + */ + @Nonnull + protected > ThisT addRequestUpdate( + final Function> serviceMethod, + final EntityT entity ) + { + final FluentHelperUpdate update = serviceMethod.apply(entity); + return addRequest(update); + } + + /** + * Adds an update operation to the current changeset request. + * + * @param updateRequest + * The {@link FluentHelperUpdate} that represents the update operation. + * @return The current fluent helper instance. + */ + @Nonnull + protected ThisT addRequest( @Nonnull final FluentHelperUpdate updateRequest ) + { + final ODataRequestUpdate request = updateRequest.toRequest(); + basicBatchFluentHelper.requestMapping.put(updateRequest, request); + + final BatchRequestChangeSetOperation operation = + new BatchRequestChangeSetOperation(( changeSet ) -> changeSet.addUpdate(request), updateRequest); + + operations.add(operation); + return getThis(); + } + + @Nonnull + @Override + public FluentHelperBatchT endChangeSet() + { + final BatchRequestChangeSet changeSet = new BatchRequestChangeSet(operations); + basicBatchFluentHelper.addChangeSet(changeSet); + return typedBatchFluentHelper; + } + + /** + * Adds a function import call to the currently opened changeset. Only use {@code executeRequest} to issue the batch + * request. + * + * @param functionImport + * The {@link FluentHelperFunction} that represents the function import call + * @return The same fluent helper + * @throws IllegalStateException + * If the batch request contains a function import call within a change set which uses the HTTP GET + * method. + */ + @Nonnull + public ThisT addFunctionImport( @Nonnull final FluentHelperFunction functionImport ) + { + final ODataRequestGeneric request = functionImport.toRequest(); + basicBatchFluentHelper.requestMapping.put(functionImport, request); + + final Consumer changesetConsumer; + + if( request instanceof ODataRequestAction ) { + changesetConsumer = changeset -> changeset.addAction((ODataRequestAction) request); + } else { + throw new IllegalStateException( + "Request for function imports in batch change sets must be " + + ODataRequestAction.class.getSimpleName() + + ", but was " + + request.getClass().getSimpleName() + + ". Only function imports using HTTP POST are allowed."); + } + + final BatchRequestChangeSetOperation operation = + new BatchRequestChangeSetOperation(changesetConsumer, functionImport); + + operations.add(operation); + + return getThis(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java new file mode 100644 index 0000000000..7668ea7640 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java @@ -0,0 +1,202 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestAction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperCreate; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperDelete; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperModification; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperUpdate; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of any OData batch request as a fluent interface for managing changesets and + * {@link #executeRequest(Destination) executing} them in a single query. + * + * @param + * The fluent helper type. + * @param + * The type of the changesets being managed in this OData batch request. + */ +public abstract class BatchFluentHelperBasic, FluentHelperBatchChangeSetT extends FluentHelperBatchEndChangeSet> + implements + FluentHelperServiceBatch +{ + @Getter( AccessLevel.PACKAGE ) + private final List requestParts = new ArrayList<>(); + final Map, Integer> requestMappingLegacy = new IdentityHashMap<>(); + @Getter( AccessLevel.PACKAGE ) + final Map, ODataRequestGeneric> requestMapping = new IdentityHashMap<>(); + + Supplier uuidProvider = UUID::randomUUID; + + /** + * Get the OData service endpoint path for the current OData batch request. Usually it can be found as static member + * DEFAULT_SERVICE_PATH in the service class. + * + * @return The String representation of the OData service endpoint path. + */ + @Nonnull + protected abstract String getServicePathForBatchRequest(); + + /** + * Method to safely return the current fluent helper instance upon public method calls. + * + * @return The current fluent helper instance. + */ + @Nonnull + protected abstract FluentHelperBatchT getThis(); + + @Nonnull + @Override + public BatchResponse executeRequest( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + // TODO Consider allowing custom Headers + + @SuppressWarnings( "PMD.CloseResource" ) // The ODataRequestResultMultipartGeneric is closed by DefaultBatchResponseResult + final ODataRequestResultMultipartGeneric result = toRequest().execute(httpClient); + + return DefaultBatchResponseResult.of(requestParts, requestMapping, result); + } + + /** + * Translate this OData v2 batch request into a generic {@link ODataRequestBatch}. + * + * @return A protocol agnostic OData batch request instance. + */ + @Nonnull + public ODataRequestBatch toRequest() + { + final String servicePath = getServicePathForBatchRequest(); + final ODataRequestBatch requestBatch = new ODataRequestBatch(servicePath, ODataProtocol.V2, uuidProvider); + + for( final BatchRequestOperation part : requestParts ) { + part.addToRequestBuilder(requestBatch); + } + return requestBatch; + } + + /** + * Method handler to register a finished changeset definition. + * + * @param changeSet + * Instance of the changeset, containing OData operations modifying entities. + */ + void addChangeSet( @Nonnull final BatchRequestChangeSet changeSet ) + { + requestParts.add(changeSet); + } + + @Nonnull + @Override + public FluentHelperBatchT addReadOperations( @Nonnull final FluentHelperRead... readOperations ) + { + for( final FluentHelperRead operation : readOperations ) { + final ODataRequestRead request = operation.toRequest(); + requestMappingLegacy.put(operation, requestParts.size()); + requestMapping.put(operation, request); + requestParts.add(new BatchRequestRead.GetAll(operation, request)); + } + return getThis(); + } + + @Nonnull + @Override + public FluentHelperBatchT addReadOperations( @Nonnull final FluentHelperByKey... readByKeyOperations ) + { + for( final FluentHelperByKey operation : readByKeyOperations ) { + final ODataRequestReadByKey request = operation.toRequest(); + requestMappingLegacy.put(operation, requestParts.size()); + requestMapping.put(operation, request); + requestParts.add(new BatchRequestRead.GetByKey(operation, request)); + } + return getThis(); + } + + @Nonnull + @Override + public FluentHelperBatchT addReadOperations( @Nonnull final FluentHelperFunction... functionOperations ) + { + for( final FluentHelperFunction operation : functionOperations ) { + final ODataRequestGeneric request = operation.toRequest(); + if( request instanceof ODataRequestAction ) { + throw new IllegalStateException( + "Request for function imports while adding read operations must be " + + ODataRequestFunction.class.getSimpleName() + + ", but was " + + request.getClass().getSimpleName() + + ". Only function imports using HTTP GET are allowed."); + } + requestMappingLegacy.put(operation, requestParts.size()); + requestMapping.put(operation, request); + requestParts.add(new BatchRequestRead.GetFunctionRequest(operation, (ODataRequestFunction) request)); + } + return getThis(); + } + + @Nonnull + @Override + public FluentHelperBatchT addChangeSet( @Nonnull final FluentHelperModification... modifications ) + { + final FluentHelperBatchChangeSetT changeSet = beginChangeSet(); + + if( !(changeSet instanceof BatchChangeSetFluentHelperBasic) ) { + throw new UnsupportedOperationException( + String + .format( + "%1$s::beginChangeSet does not return an instance of type %2$s. To fix this issue, you may either implement %1$s::addChangeSet yourself, or use the default implementation of %3$s::beginChangeSet.", + getClass().getName(), + BatchChangeSetFluentHelperBasic.class.getName(), + BatchFluentHelperBasic.class.getName())); + } + + final BatchChangeSetFluentHelperBasic mutableChangeSet = + (BatchChangeSetFluentHelperBasic) changeSet; + + for( final FluentHelperModification modification : modifications ) { + final ODataRequestGeneric request = modification.toRequest(); + requestMapping.put(modification, request); + requestMappingLegacy.put(modification, requestMappingLegacy.size()); + + if( modification instanceof FluentHelperCreate ) { + mutableChangeSet.addRequest((FluentHelperCreate) modification); + } else if( modification instanceof FluentHelperUpdate ) { + mutableChangeSet.addRequest((FluentHelperUpdate) modification); + } else if( modification instanceof FluentHelperDelete ) { + mutableChangeSet.addRequest((FluentHelperDelete) modification); + } else { + throw new IllegalArgumentException( + "Failed to add unknown type of modifying operation to OData batch request: " + + modification.getClass().getSimpleName()); + } + } + + changeSet.endChangeSet(); + return getThis(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.java new file mode 100644 index 0000000000..d92bbdbdfd --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.java @@ -0,0 +1,32 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Instance of a OData batch request changeset, defined by one or many OData operations. + */ +@RequiredArgsConstructor +class BatchRequestChangeSet implements BatchRequestOperation +{ + @Getter( AccessLevel.PACKAGE ) + @Nonnull + private final List operations; + + @Override + public void addToRequestBuilder( @Nonnull final ODataRequestBatch requestBatch ) + { + final ODataRequestBatch.Changeset requestChangeset = requestBatch.beginChangeset(); + for( final BatchRequestChangeSetOperation operation : getOperations() ) { + operation.getChangeSetConsumer().accept(requestChangeset); + } + requestChangeset.endChangeset(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.java new file mode 100644 index 0000000000..f5bc12d6b3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.java @@ -0,0 +1,18 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.function.Consumer; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperBasic; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter( AccessLevel.PACKAGE ) +class BatchRequestChangeSetOperation +{ + private final Consumer changeSetConsumer; + private final FluentHelperBasic fluentHelper; +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.java new file mode 100644 index 0000000000..860d7bf8c6 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.java @@ -0,0 +1,13 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; + +/** + * Common interface for items on root level of an OData batch request. + */ +interface BatchRequestOperation +{ + void addToRequestBuilder( @Nonnull final ODataRequestBatch requestBatch ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.java new file mode 100644 index 0000000000..61c115a5f1 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.java @@ -0,0 +1,59 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +abstract class BatchRequestRead implements BatchRequestOperation +{ + @Value + @EqualsAndHashCode( callSuper = true ) + static class GetAll extends BatchRequestRead + { + FluentHelperRead fluentHelper; + ODataRequestRead request; + + @Override + public void addToRequestBuilder( @Nonnull final ODataRequestBatch builder ) + { + builder.addRead(request); + } + } + + @Value + @EqualsAndHashCode( callSuper = true ) + static class GetByKey extends BatchRequestRead + { + FluentHelperByKey fluentHelper; + ODataRequestReadByKey request; + + @Override + public void addToRequestBuilder( @Nonnull final ODataRequestBatch builder ) + { + builder.addReadByKey(request); + } + } + + @Value + @EqualsAndHashCode( callSuper = true ) + static class GetFunctionRequest extends BatchRequestRead + { + FluentHelperFunction fluentHelper; + ODataRequestFunction request; + + @Override + public void addToRequestBuilder( @Nonnull final ODataRequestBatch builder ) + { + builder.addFunction(request); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.java new file mode 100644 index 0000000000..43b0b4f864 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.java @@ -0,0 +1,102 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.helper.CollectionValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; +import com.sap.cloud.sdk.datamodel.odata.helper.SingleValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; + +import io.vavr.control.Try; + +/** + * Interface to access the OData batch response. + */ +public interface BatchResponse extends AutoCloseable +{ + /** + * Get the result for a single changeset. + * + * @param index + * The zero-based index of the selected changeset. + * @return A wrapper of the changeset result. It can be checked for errors upon evaluation. + */ + @Nonnull + Try get( int index ); + + /** + * Convenience method to get the result for a read request on the OData batch response. + * + * @param helper + * The original fluent helper instance that was used to create the request. + * @param + * The generic entity type. + * @return A list of entities according to the original request. + */ + @Nonnull + default > List getReadResult( + @Nonnull final FluentHelperRead helper ) + { + throw new UnsupportedOperationException(); + } + + /** + * Convenience method to get the result for a read-by-key request on the OData batch response. + * + * @param helper + * The original fluent helper instance that was used to create the request. + * @param + * The generic entity type. + * @return A single entity according to the original request. + */ + @Nonnull + default < + EntityT extends VdmEntity> EntityT getReadResult( @Nonnull final FluentHelperByKey helper ) + { + throw new UnsupportedOperationException(); + } + + /** + * Convenience method to get the result for a function import request that returns a single primitive value or + * entity on the OData batch response. + * + * @param helper + * The original fluent helper instance that was used to create the request. + * @param + * The result type of the function import request + * @return A single primitive value or entity according to the original request + */ + @Nonnull + default ResultT getReadResult( @Nonnull final SingleValuedFluentHelperFunction helper ) + { + throw new UnsupportedOperationException(); + } + + /** + * Convenience method to get the result for a function import request that returns a collection of primitive values + * or entities on the OData batch response. + * + * @param helper + * The original fluent helper instance that was used to create the request. + * @param + * The result type of the function import request + * @return A collection of primitive values or entities according to the original request + */ + @Nonnull + default List getReadResult( + @Nonnull final CollectionValuedFluentHelperFunction helper ) + { + throw new UnsupportedOperationException(); + } + + /** + * Closes the underlying HTTP response entity. + * + * @since 4.15.0 + */ + @Override + void close(); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.java new file mode 100644 index 0000000000..36c20acbbe --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.java @@ -0,0 +1,22 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; + +/** + * Interface to evaluate the response of a single changeset from an OData batch response. + */ +public interface BatchResponseChangeSet +{ + /** + * Get all newly created entities from this changeset. + * + * @return A list of generic {@link VdmEntity} instances. The consumer can type-check, evaluate and cast its + * entries. + */ + @Nonnull + List> getCreatedEntities(); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.java new file mode 100644 index 0000000000..8c61d0f23c --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.java @@ -0,0 +1,20 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +/** + * Interface to expose the batch feature for service class. + * + * @param + * The type of the Batch instance. + */ +public interface BatchService +{ + /** + * Instantiate a new FluentHelper instance for a single OData batch request. + * + * @return A new instance of an OData batch request associated with the service object. + */ + @Nonnull + FluentHelperBatchT batch(); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.java new file mode 100644 index 0000000000..648d19620d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.java @@ -0,0 +1,30 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperCreate; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; + +import lombok.Value; + +@Value +class DefaultBatchResponseChangeSet implements BatchResponseChangeSet +{ + List changesetOperations; + Function> changesetEntityExtractor; + + @Nonnull + @Override + public List> getCreatedEntities() + { + return changesetOperations + .stream() + .filter(req -> req.getFluentHelper() instanceof FluentHelperCreate) + .map(changesetEntityExtractor) + .collect(Collectors.toList()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.java new file mode 100644 index 0000000000..1dafddcc88 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.java @@ -0,0 +1,134 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odata.helper.CollectionValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; +import com.sap.cloud.sdk.datamodel.odata.helper.SingleValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntityUtil; + +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for Batch response. + * + */ +@Slf4j +@RequiredArgsConstructor( staticName = "of", access = AccessLevel.PACKAGE ) +public class DefaultBatchResponseResult implements BatchResponse +{ + private final List requestParts; + private final Map, ODataRequestGeneric> requestMapping; + private final ODataRequestResultMultipartGeneric result; + + /** + * Static factory method to convert from generic response to typed response. + * + * @param response + * The generic response that should be converted. + * @param initialRequest + * The initial BatchRequest + * @return The typed (high-level) BatchResponse object. + */ + @Nonnull + public static DefaultBatchResponseResult of( + @Nonnull final ODataRequestResultMultipartGeneric response, + @Nonnull final BatchFluentHelperBasic initialRequest ) + { + return DefaultBatchResponseResult + .of(initialRequest.getRequestParts(), initialRequest.getRequestMapping(), response); + } + + @Nonnull + @Override + public Try get( final int index ) + { + final BatchRequestChangeSet requests = getRequestPartByTypeAndIndex(BatchRequestChangeSet.class, index); + if( requests == null ) { + final String errorMessage = "Unable to find changeset " + index + " in batch request."; + return Try.failure(new IllegalArgumentException(errorMessage)); + } + + // perform health check for first operation response in changeset + final FluentHelperBasic vdmRequest = requests.getOperations().get(0).getFluentHelper(); + final ODataRequestGeneric request = requestMapping.get(vdmRequest); + final Try testParse = Try.run(() -> result.getResult(request)); + if( testParse.isFailure() ) { + return Try.failure(testParse.getCause()); + } + + return Try.success(new DefaultBatchResponseChangeSet(requests.getOperations(), this::getResultingEntity)); + } + + @Nonnull + private VdmEntity getResultingEntity( @Nonnull final BatchRequestChangeSetOperation req ) + { + final ODataRequestGeneric request = requestMapping.get(req.getFluentHelper()); + final Class> entityClass = VdmEntityUtil.getEntityClass(req.getFluentHelper()); + return result.getResult(request).as(entityClass); + } + + @Nonnull + @Override + public > List getReadResult( + @Nonnull final FluentHelperRead helper ) + { + final Class entityClass = VdmEntityUtil.getEntityClass(helper); + return result.getResult(requestMapping.get(helper)).asList(entityClass); + } + + @Nonnull + @Override + public < + EntityT extends VdmEntity> EntityT getReadResult( @Nonnull final FluentHelperByKey helper ) + { + final Class entityClass = VdmEntityUtil.getEntityClass(helper); + return result.getResult(requestMapping.get(helper)).as(entityClass); + } + + @Nonnull + @Override + public ResultT getReadResult( @Nonnull final SingleValuedFluentHelperFunction helper ) + { + final Class resultClass = VdmEntityUtil.getEntityClass(helper); + return result.getResult(requestMapping.get(helper)).as(resultClass); + } + + @Nonnull + @Override + public List getReadResult( + @Nonnull final CollectionValuedFluentHelperFunction helper ) + { + final Class resultClass = VdmEntityUtil.getEntityClass(helper); + return result.getResult(requestMapping.get(helper)).asList(resultClass); + } + + @Nullable + private T getRequestPartByTypeAndIndex( final Class type, final int index ) + { + return requestParts.stream().filter(type::isInstance).map(type::cast).skip(index).findFirst().orElse(null); + } + + /** + * Closes the underlying HTTP response entity. + * + * @since 4.15.0 + */ + @Override + public void close() + { + result.close(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.java new file mode 100644 index 0000000000..4bf49c48e9 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.java @@ -0,0 +1,28 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; + +/** + * Contains methods applicable to enrich the currently open change set. + * + * @param + * The same fluent helper + */ +public interface FluentHelperBatchChangeSet +{ + /** + * Adds a function import call to the currently opened changeset. Only use {@code executeRequest} to issue the batch + * request. + * + * @param functionImport + * The {@link FluentHelperFunction} that represents the function import call + * @return The same fluent helper + * @throws IllegalStateException + * If the batch request contains a function import call within a change set which uses the HTTP GET + * method. + */ + @Nonnull + FluentHelperT addFunctionImport( @Nonnull final FluentHelperFunction functionImport ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.java new file mode 100644 index 0000000000..1d25c30f66 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.java @@ -0,0 +1,21 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +/** + * Interface to finish the definition of a single changeset. + * + * @param + * Type of the implementing OData batch request class + */ +public interface FluentHelperBatchEndChangeSet +{ + /** + * Finish the definition of a single changeset and return to the parent OData batch request instance. All changesets + * will be evaluated independently from each other. + * + * @return The current OData batch request instance. + */ + @Nonnull + FluentHelperT endChangeSet(); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.java new file mode 100644 index 0000000000..ba6f123deb --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.java @@ -0,0 +1,71 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperModification; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; + +/** + * Interface to the batch object of an OData service. + * + * @param + * Type of the implementing OData batch request class + * @param + * Type of the associated changeset class + */ +public interface FluentHelperServiceBatch> + extends + FluentHelperServiceBatchExecute +{ + /** + * Method to define a new OData batch changeset. Modifying operations on entities in a batch request can only be + * done as part of a changeset. All changesets will be evaluated independently from each other. + * + * @return A new instance of a batch changeset associated with the OData service. + */ + @Nonnull + FluentHelperChangeSetT beginChangeSet(); + + /** + * Add read request to the OData batch request builder. + * + * @param readOperations + * A var-arg array of read operations. + * @return The current OData batch request instance. + */ + @Nonnull + FluentHelperT addReadOperations( @Nonnull final FluentHelperRead... readOperations ); + + /** + * Adds a single OData batch changeset that includes all of the given data modification requests. All changesets + * will be evaluated independently from each other. + * + * @param modifications + * The data modification requests to be performed as part of a single changeset. + * @return The current OData batch request instance. + */ + @Nonnull + FluentHelperT addChangeSet( @Nonnull final FluentHelperModification... modifications ); + + /** + * Add read-by-key request to the OData batch request builder. + * + * @param readByKeyOperations + * A var-arg array of read operations. + * @return The current OData batch request instance. + */ + @Nonnull + FluentHelperT addReadOperations( @Nonnull final FluentHelperByKey... readByKeyOperations ); + + /** + * Add function requests to the OData batch request builder.Only functions that use GET can be added. + * + * @param functionOperations + * A var-arg array of function operations. + * @return The current OData batch request instance + */ + @Nonnull + FluentHelperT addReadOperations( @Nonnull final FluentHelperFunction... functionOperations ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.java new file mode 100644 index 0000000000..a832209529 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.java @@ -0,0 +1,23 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; + +/** + * Interface to provide execute methods. + * + */ +public interface FluentHelperServiceBatchExecute +{ + /** + * Executes the underlying batch query including the stored changeset operations. + * + * @param destination + * Destination object for resolving the {@code HttpClient} when executing the underlying OData query. + * + * @return A single result element, holding each changeset response. + */ + @Nonnull + BatchResponse executeRequest( @Nonnull final Destination destination ); +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.java new file mode 100644 index 0000000000..a33dbb04b5 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.java @@ -0,0 +1,87 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Abstract base class to be used to easily parse fields which can be read as a {@link Calendar} object as a Gson + * {@link TypeAdapter}. + *

+ * This may be used to specify an adapter for the new Java date API (e.g. {@link java.time.LocalDateTime}) based on a + * common conversion logic. This way the logic is split the following way: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
General Conversion logicAbstractTypeConverter subclasses (*CalendarConverter)
Gson AdapterAbstractCalendarAdapter subclasses
Jackson (De)SerializerAbstractJacksonCalendar(De)Serializer and subclasses
+ * + * @param + * The type this adapter should parse. + */ +public abstract class AbstractCalendarAdapter extends TypeAdapter +{ + /** + * Converts a string value read from a json property as an instance of the type to be created by this adapter. + * + * @param jsonString + * The string value of a json property. + * @return A {@code ConvertedObject} instance containing the resulting object, or is empty if conversion was not + * possible. + */ + @Nonnull + protected abstract ConvertedObject convertStringToType( @Nonnull final String jsonString ); + + /** + * Converts an instance of the type handled by this adapter into a string that can be written as a json value. + * + * @param entity + * The entity to convert into a String + * @return A {@code ConvertedObject} instance containing the resulting string, or is empty if conversion was not + * possible. + */ + @Nonnull + protected abstract ConvertedObject convertTypeToString( @Nullable final T entity ); + + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final T value ) + throws IOException + { + final ConvertedObject maybeString = convertTypeToString(value); + if( maybeString.isConvertible() ) { + out.value(maybeString.get()); + } + } + + @Override + @Nullable + public T read( @Nonnull final JsonReader jsonReader ) + throws IOException + { + if( jsonReader.peek() != JsonToken.STRING ) { + jsonReader.skipValue(); + return null; + } + + final String jsonValue = jsonReader.nextString(); + + return convertStringToType(jsonValue).orNull(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.java new file mode 100644 index 0000000000..eaefefcddf --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.java @@ -0,0 +1,89 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Abstract base class to be used to easily read fields which can be read as a {@link Calendar} object as a Jackson + * {@link StdDeserializer}. + *

+ * This may be used to specify a deserializer for the new Java date API (e.g. {@link java.time.LocalDateTime}) based on + * a common conversion logic. This way the logic is split the following way: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
General Conversion logicAbstractTypeConverter subclasses (*CalendarConverter)
Gson AdapterAbstractCalendarAdapter subclasses
Jackson (De)SerializerAbstractJacksonCalendar(De)Serializer and subclasses
+ *

+ * + * @param + * The type this deserializer should read. + */ +public abstract class AbstractJacksonCalendarDeserializer extends StdDeserializer +{ + private static final long serialVersionUID = -3931503330406820250L; + + /** + * Constructor needed by the super class. + * + * @param vc + * The class to be read by this deserializer. + */ + protected AbstractJacksonCalendarDeserializer( final Class vc ) + { + super(vc); + } + + /** + * Getter for an instance of the common conversion logic from and to {@code Calendar}. + * + * @return The conversion logic for this deserializer. + */ + @Nonnull + protected abstract AbstractTypeConverter getCalendarConverterInstance(); + + /** + * Getter for the conversion of a String into a Calendar object. The selection of implementation depends on what + * kind of date object the string value should represent. + * + * @return {@code AbstractTypeConverter} instance that converts a String into a Calendar. + */ + @Nonnull + protected abstract AbstractTypeConverter getStringCalendarConverterInstance(); + + @Override + @Nullable + public T deserialize( + @Nonnull final JsonParser jsonParser, + @Nonnull final DeserializationContext deserializationContext ) + throws IOException + { + final ConvertedObject cal = getStringCalendarConverterInstance().toDomain(jsonParser.getText()); + + try { + return getCalendarConverterInstance().fromDomain(cal.orNull()).orNull(); + } + catch( final Exception e ) { + throw new IOException("Could not convert the read Calendar: " + cal, e); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.java new file mode 100644 index 0000000000..b2ff89fd5e --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.java @@ -0,0 +1,88 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.databind.ser.std.StringSerializer; +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Abstract base class to be used to easily write fields which can be read as a {@link Calendar} object as a Jackson + * {@link StdSerializer}. + *

+ * This may be used to specify a deserializer for the new Java date API (e.g. {@link java.time.LocalDateTime}) based on + * a common conversion logic. This way the logic is split the following way: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
General Conversion logicAbstractTypeConverter subclasses (*CalendarConverter)
Gson AdapterAbstractCalendarAdapter subclasses
Jackson (De)SerializerAbstractJacksonCalendar(De)Serializer and subclasses
+ *

+ * + * @param + * The type this serializer should read. + */ +public abstract class AbstractJacksonCalendarSerializer extends StdSerializer +{ + private static final long serialVersionUID = -7620182674695291969L; + + /** + * Constructor needed by the super class. + * + * @param t + * The class to be written by this serializer. + */ + protected AbstractJacksonCalendarSerializer( final Class t ) + { + super(t); + } + + /** + * Getter for an instance of the common conversion logic from and to {@code Calendar}. + * + * @return The conversion logic for this serializer. + */ + @Nonnull + protected abstract AbstractTypeConverter getConverterInstance(); + + /** + * Getter for the conversion of a Calendar object into a String. The selection of implementation depends on what + * kind of date object the string value should represent. + * + * @return {@code AbstractTypeConverter} instance that converts a Calendar into a String. + */ + @Nonnull + protected abstract AbstractTypeConverter getStringCalendarConverterInstance(); + + @Override + public void serialize( + @Nullable final T value, + @Nonnull final JsonGenerator jsonGenerator, + @Nonnull final SerializerProvider serializerProvider ) + throws IOException + { + final Calendar cal = getConverterInstance().toDomain(value).orNull(); + final ConvertedObject maybeJson = getStringCalendarConverterInstance().fromDomain(cal); + + if( maybeJson.isConvertible() ) { + new StringSerializer().serialize(maybeJson.get(), jsonGenerator, serializerProvider); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.java new file mode 100644 index 0000000000..3ea18c1139 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.java @@ -0,0 +1,41 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.typeconverter.ConvertedObject; +import com.sap.cloud.sdk.typeconverter.TypeConverter; + +/** + * Default implementation of the {@link TypeConverter} interface, returning all given objects unchanged. + */ +public class IdentityConverter implements TypeConverter +{ + @Override + @Nonnull + public ConvertedObject toDomain( @Nullable final Object object ) + { + return ConvertedObject.of(object); + } + + @Override + @Nonnull + public ConvertedObject fromDomain( @Nullable final Object domainObject ) + { + return ConvertedObject.of(domainObject); + } + + @Override + @Nonnull + public Class getType() + { + return Object.class; + } + + @Override + @Nonnull + public Class getDomainType() + { + return Object.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.java new file mode 100644 index 0000000000..cb8790412c --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to read {@link LocalDateTime} fields, based on a common logic reading from + * {@link Calendar}. + */ +public class JacksonLocalDateTimeDeserializer extends AbstractJacksonCalendarDeserializer +{ + private static final long serialVersionUID = 2970121420707666471L; + + /** + * Default constructor needed by the framework. + */ + public JacksonLocalDateTimeDeserializer() + { + super(LocalDateTime.class); + } + + @Nonnull + @Override + protected AbstractTypeConverter getCalendarConverterInstance() + { + return new LocalDateTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataDateTimeStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.java new file mode 100644 index 0000000000..56f8442d39 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to write {@link LocalDateTime} fields, based on a common logic writing a + * {@link Calendar}. + */ +public class JacksonLocalDateTimeSerializer extends AbstractJacksonCalendarSerializer +{ + private static final long serialVersionUID = 17342648935256631L; + + /** + * Default constructor needed by the framework. + */ + protected JacksonLocalDateTimeSerializer() + { + super(LocalDateTime.class); + } + + @Override + @Nonnull + protected AbstractTypeConverter getConverterInstance() + { + return new LocalDateTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataDateTimeStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.java new file mode 100644 index 0000000000..e604abc7ff --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to read {@link LocalTime} fields, based on a common logic reading from + * {@link Calendar}. + */ +public class JacksonLocalTimeDeserializer extends AbstractJacksonCalendarDeserializer +{ + private static final long serialVersionUID = -6461230022477505904L; + + /** + * Default constructor needed by the framework. + */ + protected JacksonLocalTimeDeserializer() + { + super(LocalTime.class); + } + + @Override + @Nonnull + protected AbstractTypeConverter getCalendarConverterInstance() + { + return new LocalTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataTimeStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.java new file mode 100644 index 0000000000..280df73f73 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to write {@link LocalTime} fields, based on a common logic writing a + * {@link Calendar}. + */ +public class JacksonLocalTimeSerializer extends AbstractJacksonCalendarSerializer +{ + private static final long serialVersionUID = 5786928813508456930L; + + /** + * Default constructor needed by the framework. + */ + protected JacksonLocalTimeSerializer() + { + super(LocalTime.class); + } + + @Override + @Nonnull + protected AbstractTypeConverter getConverterInstance() + { + return new LocalTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataTimeStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.java new file mode 100644 index 0000000000..37cfdb63a6 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.ZonedDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to read {@link ZonedDateTime} fields, based on a common logic reading from + * {@link Calendar}. + */ +public class JacksonZonedDateTimeDeserializer extends AbstractJacksonCalendarDeserializer +{ + private static final long serialVersionUID = -7432353030570821745L; + + /** + * Default constructor needed by the framework. + */ + protected JacksonZonedDateTimeDeserializer() + { + super(ZonedDateTime.class); + } + + @Override + @Nonnull + protected AbstractTypeConverter getCalendarConverterInstance() + { + return new ZonedDateTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataDateTimeOffsetStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.java new file mode 100644 index 0000000000..24d8e61e06 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.ZonedDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; + +/** + * Jackson deserializer that is able to write {@link ZonedDateTime} fields, based on a common logic writing a + * {@link Calendar}. + */ +public class JacksonZonedDateTimeSerializer extends AbstractJacksonCalendarSerializer +{ + private static final long serialVersionUID = -3595139411304341322L; + + /** + * Default constructor needed by the framework. + */ + protected JacksonZonedDateTimeSerializer() + { + super(ZonedDateTime.class); + } + + @Override + @Nonnull + protected AbstractTypeConverter getConverterInstance() + { + return new ZonedDateTimeCalendarConverter(); + } + + @Nonnull + @Override + protected AbstractTypeConverter getStringCalendarConverterInstance() + { + return new ODataDateTimeOffsetStringCalendarConverter(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.java new file mode 100644 index 0000000000..d9c251c347 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.java @@ -0,0 +1,36 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Gson adapter to (de-)serialize fields of type {@link LocalDateTime} from and to Json. + */ +public class LocalDateTimeAdapter extends AbstractCalendarAdapter +{ + private static final ODataDateTimeStringCalendarConverter STRING_CALENDAR_CONVERTER = + new ODataDateTimeStringCalendarConverter(); + private static final LocalDateTimeCalendarConverter LOCAL_DATE_TIME_CALENDAR_CONVERTER = + new LocalDateTimeCalendarConverter(); + + @Nonnull + @Override + protected ConvertedObject convertStringToType( @Nonnull final String jsonString ) + { + final ConvertedObject maybeCalendar = STRING_CALENDAR_CONVERTER.toDomain(jsonString); + return LOCAL_DATE_TIME_CALENDAR_CONVERTER.fromDomain(maybeCalendar.orNull()); + } + + @Nonnull + @Override + protected ConvertedObject convertTypeToString( @Nullable final LocalDateTime entity ) + { + final ConvertedObject convertedCalendar = LOCAL_DATE_TIME_CALENDAR_CONVERTER.toDomain(entity); + return STRING_CALENDAR_CONVERTER.fromDomain(convertedCalendar.orNull()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.java new file mode 100644 index 0000000000..ab43eb57d0 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.java @@ -0,0 +1,73 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoField; +import java.util.Calendar; +import java.util.TimeZone; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Converts between the deprecated {@link Calendar} type and the new {@link LocalDateTime}. + *

+ * In combination with the {@link ODataField} annotation this can be used to expose fields of OData value which would be + * exposed as {@code Calendar} as {@code LocalDateTime}. + */ +public class LocalDateTimeCalendarConverter extends AbstractTypeConverter +{ + private static final int MILLI_TO_NANO_FACTOR = 1_000_000; + + @Override + @Nonnull + public ConvertedObject toDomainNonNull( @Nonnull final LocalDateTime object ) + { + // Used with the EdmDateTime class of the service SDK this calender has to be in timezone UTC, see + // Calendar.getTimeInMillis(), where the UTC usage is specified. + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + calendar.set(Calendar.YEAR, object.get(ChronoField.YEAR)); + calendar.set(Calendar.MONTH, object.get(ChronoField.MONTH_OF_YEAR) - 1); // convert 1-based to 0-based months + calendar.set(Calendar.DAY_OF_MONTH, object.get(ChronoField.DAY_OF_MONTH)); + calendar.set(Calendar.HOUR_OF_DAY, object.get(ChronoField.HOUR_OF_DAY)); + calendar.set(Calendar.MINUTE, object.get(ChronoField.MINUTE_OF_HOUR)); + calendar.set(Calendar.SECOND, object.get(ChronoField.SECOND_OF_MINUTE)); + calendar.set(Calendar.MILLISECOND, object.get(ChronoField.MILLI_OF_SECOND)); + + return ConvertedObject.of(calendar); + } + + @Override + @Nonnull + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar domainObject ) + { + final LocalDateTime localDateTime = + LocalDateTime + .of( + domainObject.get(Calendar.YEAR), + domainObject.get(Calendar.MONTH) + 1, // convert 0-based to 1-based months + domainObject.get(Calendar.DAY_OF_MONTH), + domainObject.get(Calendar.HOUR_OF_DAY), + domainObject.get(Calendar.MINUTE), + domainObject.get(Calendar.SECOND), + domainObject.get(Calendar.MILLISECOND) * MILLI_TO_NANO_FACTOR); + + return ConvertedObject.of(localDateTime); + } + + @Override + @Nonnull + public Class getType() + { + return LocalDateTime.class; + } + + @Override + @Nonnull + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.java new file mode 100644 index 0000000000..250349718a --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.java @@ -0,0 +1,35 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Gson adapter to (de-)serialize fields of type {@link LocalTime} from and to Json. + */ +public class LocalTimeAdapter extends AbstractCalendarAdapter +{ + private static final ODataTimeStringCalendarConverter TIME_STRING_CALENDAR_CONVERTER = + new ODataTimeStringCalendarConverter(); + private static final LocalTimeCalendarConverter LOCAL_TIME_CALENDAR_CONVERTER = new LocalTimeCalendarConverter(); + + @Nonnull + @Override + protected ConvertedObject convertStringToType( @Nonnull final String jsonString ) + { + final ConvertedObject maybeCalendar = TIME_STRING_CALENDAR_CONVERTER.toDomain(jsonString); + return LOCAL_TIME_CALENDAR_CONVERTER.fromDomain(maybeCalendar.orNull()); + } + + @Nonnull + @Override + protected ConvertedObject convertTypeToString( @Nullable final LocalTime entity ) + { + final ConvertedObject convertedCalendar = LOCAL_TIME_CALENDAR_CONVERTER.toDomain(entity); + return TIME_STRING_CALENDAR_CONVERTER.fromDomain(convertedCalendar.orNull()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.java new file mode 100644 index 0000000000..61f550e2d3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.java @@ -0,0 +1,71 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.LocalTime; +import java.time.temporal.ChronoField; +import java.util.Calendar; +import java.util.TimeZone; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Converts between the deprecated {@link Calendar} type and the new {@link LocalTime}. + *

+ * The year, month, and day fields on the {@code Calendar} instance are ignored/cleared, as the {@code LocalTime} does + * not contain any values for those + *

+ * In combination with the {@link ODataField} annotation this can be used to expose fields of OData value which would be + * exposed as {@code Calendar} as {@code LocalTime}. + */ +public class LocalTimeCalendarConverter extends AbstractTypeConverter +{ + private static final int MILLI_TO_NANO_FACTOR = 1_000_000; + + @Override + @Nonnull + public ConvertedObject toDomainNonNull( @Nonnull final LocalTime object ) + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + calendar.clear(); + calendar.set(Calendar.HOUR_OF_DAY, object.getHour()); + calendar.set(Calendar.MINUTE, object.getMinute()); + calendar.set(Calendar.SECOND, object.getSecond()); + if( object.isSupported(ChronoField.MILLI_OF_SECOND) ) { + calendar.set(Calendar.MILLISECOND, object.get(ChronoField.MILLI_OF_SECOND)); + } + + return ConvertedObject.of(calendar); + } + + @Override + @Nonnull + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar domainObject ) + { + final LocalTime localDateTime = + LocalTime + .of( + domainObject.get(Calendar.HOUR_OF_DAY), + domainObject.get(Calendar.MINUTE), + domainObject.get(Calendar.SECOND), + domainObject.get(Calendar.MILLISECOND) * MILLI_TO_NANO_FACTOR); + + return ConvertedObject.of(localDateTime); + } + + @Override + @Nonnull + public Class getType() + { + return LocalTime.class; + } + + @Override + @Nonnull + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.java new file mode 100644 index 0000000000..cf9706f7f7 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.java @@ -0,0 +1,54 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import lombok.extern.slf4j.Slf4j; + +/** + * For internal use only by data model classes + */ +@Slf4j +public class ODataBinaryAdapter extends TypeAdapter +{ + @Override + public void write( @Nonnull final JsonWriter jsonWriter, @Nullable final byte[] bytes ) + throws IOException + { + if( bytes == null ) { + jsonWriter.nullValue(); + return; + } + final String result = new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + jsonWriter.value(result); + } + + @Override + @Nullable + public byte[] read( @Nonnull final JsonReader jsonReader ) + throws IOException + { + if( jsonReader.peek() != JsonToken.STRING ) { + jsonReader.skipValue(); + return null; + } + + final String jsonValue = jsonReader.nextString(); + try { + return Base64.getDecoder().decode(jsonValue.getBytes(StandardCharsets.UTF_8)); + } + catch( final IllegalArgumentException e ) { + log.debug("Cannot decode String as byte array: " + e.getMessage()); + return null; + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.java new file mode 100644 index 0000000000..c2420d8694 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.java @@ -0,0 +1,43 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * For internal use only by data model classes + */ +public class ODataBooleanAdapter extends TypeAdapter +{ + /** + * For internal use only by data model classes + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final Boolean entityValue ) + throws IOException + { + out.value(entityValue); + } + + /** + * For internal use only by data model classes + */ + @Override + @Nonnull + public Boolean read( @Nonnull final JsonReader in ) + throws IOException + { + if( in.peek() == JsonToken.BOOLEAN ) { + return in.nextBoolean(); + } else { + in.skipValue(); + return Boolean.FALSE; + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.java new file mode 100644 index 0000000000..e985b6520e --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.java @@ -0,0 +1,112 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import lombok.RequiredArgsConstructor; + +/** + * For internal use only by data model classes + */ +@RequiredArgsConstructor +public class ODataCustomFieldAdapter extends TypeAdapter +{ + private final Gson gson; + + /** + * For internal use only by data model classes + */ + @Override + @Nullable + public Object read( @Nonnull final JsonReader in ) + throws IOException + { + switch( in.peek() ) { + case NUMBER: { + try { + return in.nextInt(); + } + catch( final NumberFormatException | IllegalStateException notAnInteger ) { + try { + return in.nextLong(); + } + catch( final NumberFormatException | IllegalStateException notALong ) { + return in.nextDouble(); + } + } + } + case BOOLEAN: { + return in.nextBoolean(); + } + case STRING: { + final String value = in.nextString(); + + if( !value.matches("/Date\\((-?\\p{Digit}+)\\)/") ) { + return value; + } + + return new ODataDateTimeStringCalendarConverter().toDomainNonNull(value).orNull(); + } + case BEGIN_ARRAY: { + in.beginArray(); + final List valueList = new ArrayList<>(); + + while( in.hasNext() ) { + valueList.add(read(in)); + } + + in.endArray(); + return valueList; + } + case BEGIN_OBJECT: { + in.beginObject(); + final Map valueObject = new HashMap<>(); + + while( in.hasNext() ) { + final String key = in.nextName(); + if( "__deferred".equals(key) ) { + in.skipValue(); + in.endObject(); + return null; + } else if( "__metadata".equals(key) ) { + in.skipValue(); + } else if( "results".equals(key) ) { + final Object valueList = read(in); + in.endObject(); + return valueList; + } else { + valueObject.put(key, read(in)); + } + } + + in.endObject(); + return valueObject; + } + default: { + in.skipValue(); + } + } + return null; + } + + /** + * For internal use only by data model classes + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final Object value ) + throws IOException + { + // No need to do anything here. Serialization to JSON is handled generically by Gson. + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.java new file mode 100644 index 0000000000..96f5adeb27 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.java @@ -0,0 +1,91 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.util.Calendar; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class ODataDateTimeOffsetStringCalendarConverter extends AbstractTypeConverter +{ + private static final Pattern JSON_PATTERN = + Pattern.compile("/Date\\((-?\\p{Digit}+)(?:(\\+|-)(\\p{Digit}{1,4}))?\\)/"); + + @Nonnull + @Override + public ConvertedObject toDomainNonNull( @Nonnull final String value ) + { + final Matcher jsonMatcher = JSON_PATTERN.matcher(value); + if( jsonMatcher.matches() ) { + long millis; + try { + millis = Long.parseLong(jsonMatcher.group(1)); + } + catch( final NumberFormatException e ) { + log.debug("The given date string cannot be converted to milliseconds: " + e.getMessage()); + return ConvertedObject.ofNotConvertible(); + } + + String timeZone = "GMT"; + if( jsonMatcher.group(2) != null ) { + final int offsetInMinutes = Integer.parseInt(jsonMatcher.group(3)); + if( offsetInMinutes >= 24 * 60 ) { + log.debug("The given offset is higher than minutes in a day: " + offsetInMinutes); + return ConvertedObject.ofNotConvertible(); + } + if( offsetInMinutes != 0 ) { + timeZone += + jsonMatcher.group(2) + offsetInMinutes / 60 + ":" + String.format("%02d", offsetInMinutes % 60); + // Convert the local-time milliseconds to UTC. + millis -= ("+".equals(jsonMatcher.group(2)) ? 1 : -1) * offsetInMinutes * 60 * 1000L; + } + } + final Calendar dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); + dateTimeValue.clear(); + dateTimeValue.setTimeInMillis(millis); + return ConvertedObject.of(dateTimeValue); + } + + return ConvertedObject.ofNotConvertible(); + } + + @Nonnull + @Override + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar value ) + { + // number of milliseconds since 1970-01-01T00:00:00Z + long milliSeconds = value.getTimeInMillis(); + // offset in milliseconds from GMT to the requested time zone + final int offset = value.get(Calendar.ZONE_OFFSET) + value.get(Calendar.DST_OFFSET); + + milliSeconds += offset; // Convert from UTC to local time. + final int offsetInMinutes = offset / 60 / 1000; + + if( offset == 0 ) { + return ConvertedObject.of("/Date(" + milliSeconds + ")/"); + } + + return ConvertedObject.of("/Date(" + milliSeconds + String.format("%+05d", offsetInMinutes) + ")/"); + } + + @Nonnull + @Override + public Class getType() + { + return String.class; + } + + @Nonnull + @Override + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.java new file mode 100644 index 0000000000..3a0a8ce05d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.java @@ -0,0 +1,66 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.util.Calendar; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class ODataDateTimeStringCalendarConverter extends AbstractTypeConverter +{ + private static final Pattern JSON_PATTERN = Pattern.compile("/Date\\((-?\\p{Digit}+)\\)/"); + + @Nonnull + @Override + public ConvertedObject toDomainNonNull( @Nonnull final String value ) + { + final Matcher matcher = JSON_PATTERN.matcher(value); + if( !matcher.matches() ) { + return ConvertedObject.ofNotConvertible(); + } + + final long millis; + try { + millis = Long.parseLong(matcher.group(1)); + } + catch( final NumberFormatException e ) { + log.debug("The given date string cannot be converted to milliseconds: " + e.getMessage()); + return ConvertedObject.ofNotConvertible(); + } + final Calendar dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + dateTimeValue.clear(); + dateTimeValue.setTimeInMillis(millis); + return ConvertedObject.of(dateTimeValue); + + } + + @Nonnull + @Override + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar value ) + { + final long timeInMillis = value.getTimeInMillis(); + + return ConvertedObject.of("/Date(" + timeInMillis + ")/"); + } + + @Nonnull + @Override + public Class getType() + { + return String.class; + } + + @Nonnull + @Override + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.java new file mode 100644 index 0000000000..7e02ac7a50 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.java @@ -0,0 +1,36 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.TypeConverter; + +/** + * Annotation to be used to link fields to their OData property as well as converting between the domain type of a field + * and the actually exposed type. + */ +@Target( ElementType.FIELD ) +@Retention( RetentionPolicy.RUNTIME ) +@Documented +public @interface ODataField { + /** + * The name of the OData property this field gets mapped to. + * + * @return The name of the corresponding OData property. + */ + @Nonnull + String odataName(); + + /** + * The converter to be used to convert between the domain and the exposed type of the annotated field. + * + * @return The type of the converter to use for the annotated field. + */ + @Nonnull + Class> converter() default IdentityConverter.class; +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.java new file mode 100644 index 0000000000..6c3f03bf10 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.java @@ -0,0 +1,124 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class ODataTimeStringCalendarConverter extends AbstractTypeConverter +{ + private static final Pattern PATTERN = Pattern.compile(""" + P(?:(\\p{Digit}{1,2})Y)?(?:(\\p{Digit}{1,2})M)?(?:(\\p{Digit}{1,2})D)?\ + T(?:(\\p{Digit}{1,2})H)?(?:(\\p{Digit}{1,4})M)?(?:(\\p{Digit}{1,5})(?:\\.(\\p{Digit}+?)0*)?S)?\ + """); + + @Nonnull + @Override + public ConvertedObject toDomainNonNull( @Nonnull final String jsonValue ) + { + final Matcher matcher = PATTERN.matcher(jsonValue); + if( !matcher.matches() ) { + return ConvertedObject.ofNotConvertible(); + } + if( matcher.group(1) == null + && matcher.group(2) == null + && matcher.group(3) == null + && matcher.group(4) == null + && matcher.group(5) == null + && matcher.group(6) == null ) { + return ConvertedObject.ofNotConvertible(); + } + + final Calendar dateTimeValue = Calendar.getInstance(); + dateTimeValue.clear(); + + if( matcher.group(1) != null ) { + dateTimeValue.set(Calendar.YEAR, Integer.parseInt(matcher.group(1))); + } + if( matcher.group(2) != null ) { + dateTimeValue.set(Calendar.MONTH, Integer.parseInt(matcher.group(2))); + } + if( matcher.group(3) != null ) { + dateTimeValue.set(Calendar.DAY_OF_YEAR, Integer.parseInt(matcher.group(3))); + } + dateTimeValue.set(Calendar.HOUR_OF_DAY, matcher.group(4) == null ? 0 : Integer.parseInt(matcher.group(4))); + dateTimeValue.set(Calendar.MINUTE, matcher.group(5) == null ? 0 : Integer.parseInt(matcher.group(5))); + dateTimeValue.set(Calendar.SECOND, matcher.group(6) == null ? 0 : Integer.parseInt(matcher.group(6))); + + if( matcher.group(7) != null ) { + final String decimals = matcher.group(7); + final int nanoSeconds = Integer.parseInt(decimals + "000000000".substring(decimals.length())); + if( nanoSeconds % (1000 * 1000) == 0 ) { + dateTimeValue.set(Calendar.MILLISECOND, nanoSeconds / (1000 * 1000)); + } else { + log + .debug( + "The given date has a precision that cannot be represented in milliseconds. Nanoseconds: " + + nanoSeconds); + return ConvertedObject.ofNotConvertible(); + } + } + + return ConvertedObject.of(dateTimeValue); + } + + @Nonnull + @Override + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar calendar ) + { + final StringBuilder result = new StringBuilder(21); // 21 characters are enough for nanosecond precision. + result.append('P'); + result.append('T'); + result.append(calendar.get(Calendar.HOUR_OF_DAY)); + result.append('H'); + result.append(calendar.get(Calendar.MINUTE)); + result.append('M'); + result.append(calendar.get(Calendar.SECOND)); + + final int fractionalSecs = calendar.get(Calendar.MILLISECOND); + appendFractionalSeconds(result, fractionalSecs); + result.append('S'); + + return ConvertedObject.of(result.toString()); + } + + protected static void appendFractionalSeconds( final StringBuilder result, final int fractionalSeconds ) + { + if( fractionalSeconds > 0 ) { + // Determine the number of significant digits. + int output = fractionalSeconds; + while( output % 10 == 0 ) { + output /= 10; + } + + result.append('.'); + for( int d = 100; d > 0; d /= 10 ) { + final byte digit = (byte) (fractionalSeconds % (d * 10) / d); + if( digit > 0 || fractionalSeconds % d > 0 ) { + result.append((char) ('0' + digit)); + } + } + } + } + + @Nonnull + @Override + public Class getType() + { + return String.class; + } + + @Nonnull + @Override + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.java new file mode 100644 index 0000000000..0426b5e285 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.java @@ -0,0 +1,301 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static java.util.function.Predicate.not; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmObject; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * For internal use only by data model classes + * + * @param + * The generic VDM object type + */ +@Slf4j +public class ODataVdmEntityAdapter extends TypeAdapter> +{ + @Nonnull + private final Gson gson; + + @Nonnull + private final Class entityRawType; + + @Nullable + private TypeAdapter> delegateAdapter = null; + + @Nullable + private ODataVdmEntityAdapter superClassAdapter = null; + + @Nonnull + private final TypeAdapter customFieldAdapter; + + // (1) Field has stateful accessibility flag + // (2) Multiple threads may access the Map, therefore state must be isolated with ThreadLocal + // (3) ThreadLocal is to be defined statically, therefore the properties must be mapped to a class reference + private static final ThreadLocal, Map>> fieldProperties = + ThreadLocal.withInitial(IdentityHashMap::new); + + @Nonnull + private Map getFieldProperties( @Nonnull final Class type ) + { + return fieldProperties.get().computeIfAbsent(type, ODataVdmEntityAdapter::createFieldProperties); + } + + @Nonnull + private static Map createFieldProperties( @Nonnull final Class type ) + { + final Map result = new LinkedHashMap<>(); + for( final Field field : type.getDeclaredFields() ) { + if( field.isAnnotationPresent(ElementName.class) || field.isAnnotationPresent(SerializedName.class) ) { + final String odataName = + field.isAnnotationPresent(ElementName.class) + ? field.getAnnotation(ElementName.class).value() + : field.getAnnotation(SerializedName.class).value(); + result.put(odataName, field); + } + } + return result; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + private static class ODataV2Metadata + { + @Nullable + @SerializedName( "uri" ) + @ODataField( odataName = "uri" ) + private String uri; + + @Nullable + @SerializedName( "etag" ) + @ODataField( odataName = "etag" ) + private String etag; + + @Nullable + @SerializedName( "type" ) + @ODataField( odataName = "type" ) + private String type; + } + + @Nonnull + private static TypeAdapter getAdapterFromField( @Nonnull final Field entityField, @Nonnull final Gson gson ) + { + if( entityField.isAnnotationPresent(JsonAdapter.class) ) { + try { + return (TypeAdapter) entityField + .getAnnotation(JsonAdapter.class) + .value() + .getDeclaredConstructor() + .newInstance(); + } + catch( final + InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e ) { + log.warn("Could not instantiate the field '" + entityField.getName() + "'.", e); + } + } + if( Iterable.class.isAssignableFrom(entityField.getType()) ) { + final ParameterizedType entityFieldTypeParams = (ParameterizedType) entityField.getGenericType(); + final Type listEntityType = entityFieldTypeParams.getActualTypeArguments()[0]; + final TypeAdapter innerTypeAdapter = gson.getAdapter(TypeToken.get(listEntityType)); + return new ODataVdmEntityListAdapter<>(gson, innerTypeAdapter); + } + return gson.getAdapter(entityField.getType()); + } + + /** + * For internal use only by data model classes + * + * @param adapterFactory + * The GSON type adapter factory + * @param gson + * The GSON reference + * @param entityRawType + * The generic entity raw type + */ + @SuppressWarnings( "unchecked" ) + public ODataVdmEntityAdapter( + @Nonnull final TypeAdapterFactory adapterFactory, + @Nonnull final Gson gson, + @Nonnull final Class entityRawType ) + { + this.gson = gson; + this.entityRawType = entityRawType; + customFieldAdapter = new ODataCustomFieldAdapter(gson); + + final Class entityRawSuperType = entityRawType.getSuperclass(); + if( Object.class == entityRawSuperType ) { + delegateAdapter = + (TypeAdapter>) gson.getDelegateAdapter(adapterFactory, TypeToken.get(entityRawType)); + } else { + superClassAdapter = new ODataVdmEntityAdapter<>(adapterFactory, gson, entityRawSuperType); + } + } + + /** + * For internal use only by data model classes + */ + @Override + @Nullable + public VdmObject read( @Nonnull final JsonReader jsonReader ) + throws IOException + { + try { + @SuppressWarnings( "unchecked" ) + final VdmObject entity = (VdmObject) entityRawType.getDeclaredConstructor().newInstance(); + + if( jsonReader.peek() == JsonToken.BEGIN_OBJECT ) { + jsonReader.beginObject(); + + while( jsonReader.hasNext() ) { + final String propertyKey = jsonReader.nextName(); + + if( "__metadata".equals(propertyKey) ) { + if( jsonReader.peek() == JsonToken.BEGIN_OBJECT ) { + final ODataV2Metadata metadata = new Gson().fromJson(jsonReader, ODataV2Metadata.class); + final Option maybeEtag = + Option.of(metadata).map(ODataV2Metadata::getEtag).filter(not(Strings::isNullOrEmpty)); + if( maybeEtag.isDefined() && entity instanceof VdmEntity ) { + ((VdmEntity) entity).setVersionIdentifier(maybeEtag.get()); + } + } else { + log.warn("Expected JSON value \"__metadata\" to be an object."); + jsonReader.skipValue(); + } + } else if( "__deferred".equals(propertyKey) ) { + jsonReader.skipValue(); + jsonReader.endObject(); + return null; + } else { + final Field entityField = getPropertySerializationInfo(propertyKey); + if( entityField != null ) { + final TypeAdapter fieldAdapter = getAdapterFromField(entityField, gson); + final Object attributeValue = fieldAdapter.read(jsonReader); + + // To be safe/secure, since fields are declared private in the VDM. + final boolean oldAccessibleValue = entityField.canAccess(entity); + entityField.setAccessible(true); + entityField.set(entity, attributeValue); + entityField.setAccessible(oldAccessibleValue); + } else { + entity.getCustomFields().put(propertyKey, customFieldAdapter.read(jsonReader)); + } + } + } + + jsonReader.endObject(); + } else if( jsonReader.peek() == JsonToken.NULL ) { + jsonReader.nextNull(); + return null; + } + + return entity; + } + catch( final + InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e ) { + log.error("Could not instantiate or initialize '{}'. Returning null instead.", entityRawType.getName(), e); + } + return null; + } + + @Nullable + private Field getPropertySerializationInfo( final String propertyKey ) + { + Field result = getFieldProperties(entityRawType).get(propertyKey); + if( result == null && superClassAdapter != null ) { + result = superClassAdapter.getPropertySerializationInfo(propertyKey); + } + return result; + } + + /** + * For internal use only by data model classes + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final VdmObject value ) + throws IOException + { + if( value != null ) { + final JsonObject entityAsJson = getEntityAsJsonObject(value); + final JsonObject customFieldsAsJson = gson.toJsonTree(value.getCustomFields()).getAsJsonObject(); + + for( final Map.Entry customField : customFieldsAsJson.entrySet() ) { + entityAsJson.add(customField.getKey(), customField.getValue()); + } + + gson.toJson(entityAsJson, out); + } else { + out.nullValue(); + } + } + + @SuppressWarnings( "unchecked" ) + private JsonObject getEntityAsJsonObject( final VdmObject value ) + { + if( delegateAdapter != null ) { + return delegateAdapter.toJsonTree(value).getAsJsonObject(); + } else { + final JsonObject entityAsJson = superClassAdapter.getEntityAsJsonObject(value); + + for( final Map.Entry entityProperty : getFieldProperties(entityRawType).entrySet() ) { + try { + final Field propertyField = entityProperty.getValue(); + + // To be safe/secure, since fields are declared private in the VDM. + final boolean oldAccessibleValue = propertyField.canAccess(value); + propertyField.setAccessible(true); + final Object propertyValue = propertyField.get(value); + propertyField.setAccessible(oldAccessibleValue); + + final TypeAdapter fieldAdapter = + (TypeAdapter) getAdapterFromField(propertyField, gson); + final JsonElement propertyValueAsJson = fieldAdapter.toJsonTree(propertyValue); + + // Overwrites JSON property from the superclass if this class has a property with the same name. + entityAsJson.add(entityProperty.getKey(), propertyValueAsJson); + } + catch( final IllegalAccessException e ) { + log.error("Could not serialize property '{}'. Returning null instead.", entityProperty.getKey(), e); + } + } + return entityAsJson; + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.java new file mode 100644 index 0000000000..f563087624 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.java @@ -0,0 +1,34 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmObject; + +/** + * For internal use only by data model classes. + */ +@SuppressWarnings( "unchecked" ) +public class ODataVdmEntityAdapterFactory implements TypeAdapterFactory +{ + /** + * For internal use only by data model classes. + * + * {@inheritDoc} + */ + @Override + @Nullable + public TypeAdapter create( @Nonnull final Gson gson, @Nonnull final TypeToken type ) + { + final Class entityType = type.getRawType(); + + if( VdmObject.class.isAssignableFrom(entityType) ) { + return (TypeAdapter) new ODataVdmEntityAdapter<>(this, gson, entityType); + } + return null; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.java new file mode 100644 index 0000000000..4093f95aec --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.java @@ -0,0 +1,103 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * For internal use only by data model classes + * + * @param + * The generic value type. + */ +public class ODataVdmEntityListAdapter extends TypeAdapter> +{ + private final Gson gson; + private final TypeAdapter entityAdapter; + + /** + * For internal use only by data model classes + * + * @param gson + * The GSON instance to access serialization and desrialization. + * @param entityAdapter + * The entity type adapter to be used. + */ + public ODataVdmEntityListAdapter( @Nonnull final Gson gson, @Nonnull final TypeAdapter entityAdapter ) + { + this.gson = gson; + this.entityAdapter = entityAdapter; + } + + private List readArray( final JsonReader in ) + throws IOException + { + in.beginArray(); + final List entityList = new ArrayList<>(); + + while( in.hasNext() ) { + @SuppressWarnings( "unchecked" ) + final T entity = entityAdapter.read(in); + entityList.add(entity); + } + + in.endArray(); + return entityList; + } + + /** + * For internal use only by data model classes + */ + @Override + @Nullable + public List read( @Nonnull final JsonReader in ) + throws IOException + { + List entityList = null; + if( in.peek() == JsonToken.BEGIN_OBJECT ) { + in.beginObject(); + if( in.peek() == JsonToken.NAME ) { + final String resultsKey = in.nextName(); + if( "results".equals(resultsKey) && in.peek() == JsonToken.BEGIN_ARRAY ) { + entityList = readArray(in); + } else { + in.skipValue(); + } + } + in.endObject(); + } else if( in.peek() == JsonToken.BEGIN_ARRAY ) { + entityList = readArray(in); + } else { + in.skipValue(); + } + return entityList; + } + + /** + * For internal use only by data model classes + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final List entityList ) + throws IOException + { + if( entityList != null ) { + final JsonArray entityListAsJson = new JsonArray(); + for( final T entity : entityList ) { + entityListAsJson.add(entityAdapter.toJsonTree(entity)); + } + gson.toJson(entityListAsJson, out); + } else { + out.nullValue(); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.java new file mode 100644 index 0000000000..1a2bf9eaf4 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.java @@ -0,0 +1,36 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.ZonedDateTime; +import java.util.Calendar; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Gson adapter to (de-)serialize fields of type {@link ZonedDateTime} from and to Json. + */ +public class ZonedDateTimeAdapter extends AbstractCalendarAdapter +{ + private static final ODataDateTimeOffsetStringCalendarConverter DATE_TIME_OFFSET_STRING_CALENDAR_CONVERTER = + new ODataDateTimeOffsetStringCalendarConverter(); + private static final ZonedDateTimeCalendarConverter ZONED_DATE_TIME_CALENDAR_CONVERTER = + new ZonedDateTimeCalendarConverter(); + + @Nonnull + @Override + protected ConvertedObject convertStringToType( @Nonnull final String jsonString ) + { + final ConvertedObject maybeCalendar = DATE_TIME_OFFSET_STRING_CALENDAR_CONVERTER.toDomain(jsonString); + return ZONED_DATE_TIME_CALENDAR_CONVERTER.fromDomain(maybeCalendar.orNull()); + } + + @Nonnull + @Override + protected ConvertedObject convertTypeToString( @Nullable final ZonedDateTime entity ) + { + final ConvertedObject convertedCalendar = ZONED_DATE_TIME_CALENDAR_CONVERTER.toDomain(entity); + return DATE_TIME_OFFSET_STRING_CALENDAR_CONVERTER.fromDomain(convertedCalendar.orNull()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.java new file mode 100644 index 0000000000..a9d0e2c15c --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.java @@ -0,0 +1,51 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +/** + * Converts between the deprecated {@link Calendar} type and the new {@link ZonedDateTime}. + *

+ * In combination with the {@link ODataField} annotation this can be used to expose fields of OData value which would be + * exposed as {@code Calendar} as {@code ZonedDateTime}. + */ +public class ZonedDateTimeCalendarConverter extends AbstractTypeConverter +{ + @Override + @Nonnull + public ConvertedObject toDomainNonNull( @Nonnull final ZonedDateTime object ) + { + final Calendar cal = GregorianCalendar.from(object); + return ConvertedObject.of(cal); + } + + @Override + @Nonnull + public ConvertedObject fromDomainNonNull( @Nonnull final Calendar domainObject ) + { + final TimeZone timeZone = domainObject.getTimeZone(); + final ZonedDateTime zdt = ZonedDateTime.ofInstant(domainObject.toInstant(), timeZone.toZoneId()); + return ConvertedObject.of(zdt); + } + + @Override + @Nonnull + public Class getType() + { + return ZonedDateTime.class; + } + + @Override + @Nonnull + public Class getDomainType() + { + return Calendar.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.java new file mode 100644 index 0000000000..7ee198fa07 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.java @@ -0,0 +1,17 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a field in a VDM entity as being a key field. This means that the annotated field plus any other + * fields with this annotation uniquely identify an instance of + * {@link com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity}. The VDM generator will add this annotation to the entity + * classes it creates based on the OData EDMX ({@code } tag under {@code }) + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target( ElementType.FIELD ) +public @interface Key { +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.java new file mode 100644 index 0000000000..e865b80a7b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.java @@ -0,0 +1,49 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.exception; + +import javax.annotation.Nullable; + +import lombok.NoArgsConstructor; + +/** + * Throws if a certain field cannot be found for an entity. + */ +@NoArgsConstructor +public class NoSuchEntityFieldException extends RuntimeException +{ + private static final long serialVersionUID = -5897105662911702521L; + + /** + * Initializes a new {@link NoSuchEntityFieldException} instance. + * + * @param message + * The exception message. + */ + public NoSuchEntityFieldException( @Nullable final String message ) + { + super(message); + } + + /** + * Initializes a new {@link NoSuchEntityFieldException} instance. + * + * @param cause + * The exception cause. + */ + public NoSuchEntityFieldException( @Nullable final Throwable cause ) + { + super(cause); + } + + /** + * Initializes a new {@link NoSuchEntityFieldException} instance. + * + * @param message + * The exception message. + * @param cause + * The exception cause. + */ + public NoSuchEntityFieldException( @Nullable final String message, @Nullable final Throwable cause ) + { + super(message, cause); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.java new file mode 100644 index 0000000000..ddc84b08d0 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.java @@ -0,0 +1,37 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.exception; + +import javax.annotation.Nullable; + +/** + * ODataPayloadParsingFailedException will be thrown whenever the VDM encounters errors during parsing that cannot be + * recovered from. Possible causes are when the ODataJsonMapResolver is instructed to throw exceptions when encountering + * null values or when TypeConverters for specific fields cannot be instantiated. + */ +public class ODataPayloadParsingFailedException extends RuntimeException +{ + private static final long serialVersionUID = 6446357797006978261L; + + /** + * Returns a new ODataPayloadParsingFailedException instance. + * + * @param message + * The error message. + * @param cause + * The exception causing the error. + */ + public ODataPayloadParsingFailedException( @Nullable final String message, @Nullable final Exception cause ) + { + super(message, cause); + } + + /** + * Returns a new ODataPayloadParsingFailedException instance. + * + * @param message + * The error message. + */ + public ODataPayloadParsingFailedException( @Nullable final String message ) + { + super(message); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.java new file mode 100644 index 0000000000..d0395546dd --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.java @@ -0,0 +1,77 @@ +package com.sap.cloud.sdk.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.temporal.ChronoField; +import java.util.Calendar; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeCalendarConverter; + +class LocalDateTimeCalendarConverterTest +{ + @Test + void testFromDomain() + { + final int someYear = 2017; + final int someMonth = Calendar.MAY; + final int someDay = 24; + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + + final Calendar input = Calendar.getInstance(); + input.clear(); + input.set(Calendar.YEAR, someYear); + input.set(Calendar.MONTH, someMonth); + input.set(Calendar.DAY_OF_MONTH, someDay); + input.set(Calendar.HOUR_OF_DAY, someHour); + input.set(Calendar.MINUTE, someMinute); + input.set(Calendar.SECOND, someSecond); + input.set(Calendar.MILLISECOND, someMillisecond); + + final LocalDateTime result = new LocalDateTimeCalendarConverter().fromDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.get(ChronoField.YEAR)).isEqualTo(someYear); + assertThat(result.get(ChronoField.MONTH_OF_YEAR)).isEqualTo(Month.MAY.getValue()); + assertThat(result.get(ChronoField.DAY_OF_MONTH)).isEqualTo(someDay); + assertThat(result.get(ChronoField.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(ChronoField.MINUTE_OF_HOUR)).isEqualTo(someMinute); + assertThat(result.get(ChronoField.SECOND_OF_MINUTE)).isEqualTo(someSecond); + assertThat(result.get(ChronoField.MILLI_OF_SECOND)).isEqualTo(someMillisecond); + } + + @Test + void testToDomain() + { + final int someYear = 2017; + final int someMonth = Month.MAY.getValue(); + final int someDay = 24; + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + final int millisAsNanoseconds = someMillisecond * 1_000_000; + + final LocalDateTime input = + LocalDateTime.of(someYear, someMonth, someDay, someHour, someMinute, someSecond, millisAsNanoseconds); + + final Calendar result = new LocalDateTimeCalendarConverter().toDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.get(Calendar.YEAR)).isEqualTo(someYear); + assertThat(result.get(Calendar.MONTH)).isEqualTo(Calendar.MAY); + assertThat(result.get(Calendar.DAY_OF_MONTH)).isEqualTo(someDay); + assertThat(result.get(Calendar.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(Calendar.MINUTE)).isEqualTo(someMinute); + assertThat(result.get(Calendar.SECOND)).isEqualTo(someSecond); + assertThat(result.get(Calendar.MILLISECOND)).isEqualTo(someMillisecond); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.java new file mode 100644 index 0000000000..11062fa961 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.java @@ -0,0 +1,60 @@ +package com.sap.cloud.sdk.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalTime; +import java.time.temporal.ChronoField; +import java.util.Calendar; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalTimeCalendarConverter; + +class LocalTimeCalendarConverterTest +{ + @Test + void testFromDomain() + { + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + + final Calendar input = Calendar.getInstance(); + input.clear(); + input.set(Calendar.HOUR_OF_DAY, someHour); + input.set(Calendar.MINUTE, someMinute); + input.set(Calendar.SECOND, someSecond); + input.set(Calendar.MILLISECOND, someMillisecond); + + final LocalTime result = new LocalTimeCalendarConverter().fromDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.get(ChronoField.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(ChronoField.MINUTE_OF_HOUR)).isEqualTo(someMinute); + assertThat(result.get(ChronoField.SECOND_OF_MINUTE)).isEqualTo(someSecond); + assertThat(result.get(ChronoField.MILLI_OF_SECOND)).isEqualTo(someMillisecond); + } + + @Test + void testToDomain() + { + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + final int millisAsNanoseconds = someMillisecond * 1_000_000; + + final LocalTime input = LocalTime.of(someHour, someMinute, someSecond, millisAsNanoseconds); + + final Calendar result = new LocalTimeCalendarConverter().toDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.get(Calendar.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(Calendar.MINUTE)).isEqualTo(someMinute); + assertThat(result.get(Calendar.SECOND)).isEqualTo(someSecond); + assertThat(result.get(Calendar.MILLISECOND)).isEqualTo(someMillisecond); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java new file mode 100644 index 0000000000..4572fd5764 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java @@ -0,0 +1,32 @@ +package com.sap.cloud.sdk.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.StringReader; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.Test; + +import com.google.gson.Strictness; +import com.google.gson.stream.JsonReader; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeAdapter; + +class ZonedDateTimeAdapterTest +{ + + @Test + void testDeserialization() + throws IOException + { + final String jsonInput = "\"/Date(1525730400000-0120)/\""; + + final ZonedDateTimeAdapter sut = new ZonedDateTimeAdapter(); + final JsonReader reader = new JsonReader(new StringReader(jsonInput)); + reader.setStrictness(Strictness.LENIENT); + final ZonedDateTime result = sut.read(reader); + + assertThat(result).isEqualTo(ZonedDateTime.of(2018, 5, 8, 2, 0, 0, 0, ZoneId.of("UTC+2"))); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.java new file mode 100644 index 0000000000..a8a7b118e6 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.java @@ -0,0 +1,84 @@ +package com.sap.cloud.sdk.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.util.Calendar; +import java.util.TimeZone; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeCalendarConverter; + +class ZonedDateTimeCalendarConverterTest +{ + @Test + void testFromDomain() + { + final int someYear = 2017; + final int someMonth = Calendar.MAY; + final int someDay = 24; + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + final TimeZone someTimeZone = TimeZone.getTimeZone("America/Los_Angeles"); + + final Calendar input = Calendar.getInstance(someTimeZone); + input.clear(); + input.set(Calendar.YEAR, someYear); + input.set(Calendar.MONTH, someMonth); + input.set(Calendar.DAY_OF_MONTH, someDay); + input.set(Calendar.HOUR_OF_DAY, someHour); + input.set(Calendar.MINUTE, someMinute); + input.set(Calendar.SECOND, someSecond); + input.set(Calendar.MILLISECOND, someMillisecond); + + final ZonedDateTime result = new ZonedDateTimeCalendarConverter().fromDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.getZone()).isEqualTo(someTimeZone.toZoneId()); + assertThat(result.get(ChronoField.YEAR)).isEqualTo(someYear); + assertThat(result.get(ChronoField.MONTH_OF_YEAR)).isEqualTo(Month.MAY.getValue()); + assertThat(result.get(ChronoField.DAY_OF_MONTH)).isEqualTo(someDay); + assertThat(result.get(ChronoField.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(ChronoField.MINUTE_OF_HOUR)).isEqualTo(someMinute); + assertThat(result.get(ChronoField.SECOND_OF_MINUTE)).isEqualTo(someSecond); + assertThat(result.get(ChronoField.MILLI_OF_SECOND)).isEqualTo(someMillisecond); + } + + @Test + void testToDomain() + { + final int someYear = 2017; + final int someMonth = Month.MAY.getValue(); + final int someDay = 24; + final int someHour = 15; + final int someMinute = 57; + final int someSecond = 42; + final int someMillisecond = 852; + final int millisAsNanoseconds = someMillisecond * 1_000_000; + final ZoneId someTimeZone = ZoneId.of("America/Los_Angeles"); + + final ZonedDateTime input = + ZonedDateTime + .of(someYear, someMonth, someDay, someHour, someMinute, someSecond, millisAsNanoseconds, someTimeZone); + + final Calendar result = new ZonedDateTimeCalendarConverter().toDomain(input).get(); + + assertThat(result).isNotNull(); + + assertThat(result.getTimeZone()).isEqualTo(TimeZone.getTimeZone(someTimeZone)); + assertThat(result.get(Calendar.YEAR)).isEqualTo(someYear); + assertThat(result.get(Calendar.MONTH)).isEqualTo(Calendar.MAY); + assertThat(result.get(Calendar.DAY_OF_MONTH)).isEqualTo(someDay); + assertThat(result.get(Calendar.HOUR_OF_DAY)).isEqualTo(someHour); + assertThat(result.get(Calendar.MINUTE)).isEqualTo(someMinute); + assertThat(result.get(Calendar.SECOND)).isEqualTo(someSecond); + assertThat(result.get(Calendar.MILLISECOND)).isEqualTo(someMillisecond); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.java new file mode 100644 index 0000000000..a1ffb17005 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.java @@ -0,0 +1,236 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.created; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.HttpHeaders; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorDetails; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.With; + +@WireMockTest +class CreateViaNavigationPropertyTest +{ + private static final String ODATA_SERVICE_PATH = "/service/path"; + private static final String ODATA_ENTITY = "A_TestEntity"; + private static final String ODATA_ENTITY_RELATED = "A_RelatedEntity"; + private static final String KEY = "('2')"; + private static final String RELATIONS_NAV_PROP = "to_Relations"; + + private static final String JSON_MOCK_REQUEST = "{\"Description\" :\"DE\"}"; + private static final String JSON_CREATED_RESPONSE = """ + { + "d": { + "__metadata": { + "id": "https://127.0.0.1/service/path/A_TestEntity(Name='2',Relation='652138')", + "uri": "https://127.0.0.1/service/path/A_TestEntity(Name='2',Relation='652138')", + "type": "TEST_SERVICE.A_RelatedEntityType" + } + } + } + """; + private static final String JSON_NOT_IMPLEMENTED_RESPONSE = """ + { + "error": { + "code": "AB/100", + "message": { + "lang": "en", + "value": "Invalid method invocation: 'CREATE' method is called on the non-root entity 'A_RelatedEntity'" + }, + "innererror": { + "application": { + "component_id": "AB-CDE-FGH-IJ", + "service_namespace": "/SAP/", + "service_id": "TEST_SERVICE", + "service_version": "0001" + }, + "transactionid": "10000", + "timestamp": "20180815132618.3364870", + "errordetails": [ + { + "code": "INNER-CODE-1", + "message": "INNER-MESSAGE-1", + "propertyref": "", + "severity": "error", + "target": "" + } + ] + } + } + } + """; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + stubFor(head(urlEqualTo(ODATA_SERVICE_PATH + "/")).willReturn(ok())); + } + + @Test + void happyPathWorksAsExpected() + { + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY + KEY + "/" + RELATIONS_NAV_PROP)) + .withRequestBody(equalToJson(JSON_MOCK_REQUEST)) + .willReturn(created().withBody(JSON_CREATED_RESPONSE))); + + FluentHelperFactory + .withServicePath(ODATA_SERVICE_PATH) + .create(ODATA_ENTITY, new RelatedEntity("DE")) + .asChildOf(new TestEntity().withName("2"), TestEntity.TO_RELATIONS) + .executeRequest(destination); + + verify(1, headRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/"))); + verify( + 1, + postRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY + KEY + "/" + RELATIONS_NAV_PROP))); + } + + @Test + void nullParametersResultInDefaultBehavior() + { + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY_RELATED)) + .withRequestBody(equalToJson(JSON_MOCK_REQUEST)) + .willReturn(created().withBody(JSON_CREATED_RESPONSE))); + + FluentHelperFactory + .withServicePath(ODATA_SERVICE_PATH) + .create(ODATA_ENTITY_RELATED, new RelatedEntity("DE")) + .asChildOf(null, null) + .executeRequest(destination); + + verify(1, headRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/"))); + verify(1, postRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY_RELATED))); + } + + @Test + void directCreationFails() + { + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY_RELATED)) + .withRequestBody(equalToJson(JSON_MOCK_REQUEST)) + .willReturn( + aResponse() + .withStatus(501) + .withHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .withBody(JSON_NOT_IMPLEMENTED_RESPONSE))); + + assertThatCode( + () -> FluentHelperFactory + .withServicePath(ODATA_SERVICE_PATH) + .create(ODATA_ENTITY_RELATED, new RelatedEntity("DE")) + .executeRequest(destination)) + .hasMessage( + "The HTTP response code (501) indicates an error. The OData service responded with an error message.") + .asInstanceOf(InstanceOfAssertFactories.type(ODataServiceErrorException.class)) + .matches(e -> e.getHttpCode() == 501) + .extracting(ODataServiceErrorException::getOdataError) + .satisfies(e -> { + assertThat(e.getODataCode()).isEqualTo("AB/100"); + assertThat(e.getODataMessage()).startsWith("Invalid method invocation: 'CREATE'"); + + final List details = e.getDetails(); + assertThat(details).isNotNull().hasSize(1); + assertThat(details.get(0).getODataCode()).isEqualTo("INNER-CODE-1"); + assertThat(details.get(0).getODataMessage()).isEqualTo("INNER-MESSAGE-1"); + }); + + verify(1, headRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/"))); + verify(1, postRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/" + ODATA_ENTITY_RELATED))); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @EqualsAndHashCode( callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = ODATA_ENTITY; + private final Class type = TestEntity.class; + + @SerializedName( "Name" ) + @JsonProperty( "Name" ) + @ODataField( odataName = "Name" ) + @With + private String name; + + @SerializedName( RELATIONS_NAV_PROP ) + @JsonProperty( RELATIONS_NAV_PROP ) + @ODataField( odataName = RELATIONS_NAV_PROP ) + private List relations; + + public static final TestEntityLink TO_RELATIONS = new TestEntityLink<>(RELATIONS_NAV_PROP); + + @Nonnull + @Override + protected Map getKey() + { + return Collections.singletonMap("Name", getName()); + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @EqualsAndHashCode( callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class RelatedEntity extends VdmEntity + { + private final String entityCollection = ODATA_ENTITY_RELATED; + private final Class type = RelatedEntity.class; + + @SerializedName( "Description" ) + @JsonProperty( "Description" ) + @ODataField( odataName = "Description" ) + private String description; + } + + public static class TestEntityLink> + extends + EntityLink, TestEntity, ObjectT> + { + public TestEntityLink( final String name ) + { + super(name); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.java new file mode 100644 index 0000000000..1291dac152 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.java @@ -0,0 +1,256 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDateTime; +import java.time.Month; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import javax.annotation.Nonnull; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeCalendarConverter; +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; +import com.sap.cloud.sdk.typeconverter.exception.ObjectNotConvertibleException; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@WireMockTest +class CustomFieldTypeConverterTest +{ + private static final String ODATA_ENDPOINT_URL = "/service/path"; + private static final String ENTITY_SET = "A_TestEntity"; + private static final String MY_CUSTOM_FIELD = "MyCustomField"; + private static final String RESPONSE = """ + { + "d": { + "results": [ + { + "__metadata": { + "id": "https://127.0.0.1/service/path/A_TestEntity('1')", + "uri": "https://127.0.0.1/service/path/A_TestEntity('1')", + "type": "API_TEST.A_TestEntityType" + }, + "Id": "1", + "SomeField": "123", + "ETag": "SOME_ETAG", + "MyCustomField": %1$s + } + ] + } + } + """; + + private DefaultHttpDestination destination; + + @BeforeEach + void before( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = ENTITY_SET; + private final Class type = TestEntity.class; + } + + @Getter + private static class LocalDateTimeBooleanConverter extends AbstractTypeConverter + { + private final Class type = LocalDateTime.class; + private final Class domainType = Boolean.class; + + @Nonnull + @Override + public ConvertedObject toDomainNonNull( @Nonnull final LocalDateTime object ) + throws Exception + { + throw new Exception("This implementation should fail."); + } + + @Nonnull + @Override + public ConvertedObject fromDomainNonNull( @Nonnull final Boolean domainObject ) + throws Exception + { + throw new Exception("This implementation should fail."); + } + } + + @Test + void calendarToLocalDateTimeOnGet() + { + mockResponses("\"/Date(1507075200000)/\""); + + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeCalendarConverter()); + + final TestEntity testEntity = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .read(TestEntity.class, ENTITY_SET) + .executeRequest(destination) + .get(0); + + final LocalDateTime customField = testEntity.getCustomField(myCustomField); + + assertThat(customField).isInstanceOf(LocalDateTime.class); + assertThat(customField).isEqualTo(LocalDateTime.of(2017, Month.OCTOBER, 4, 0, 0, 0)); + } + + @Test + void returnsNullOnNullValue() + { + mockResponses("null"); + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeCalendarConverter()); + + final TestEntity testEntity = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .read(TestEntity.class, ENTITY_SET) + .executeRequest(destination) + .get(0); + + final LocalDateTime customField = testEntity.getCustomField(myCustomField); + assertThat(customField).isNull(); + } + + @Test + void failsOnConversionErrorOnGet() + { + mockResponses("\"/Date(1507075200000)/\""); + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeBooleanConverter()); + + final TestEntity testEntity = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .read(TestEntity.class, ENTITY_SET) + .executeRequest(destination) + .get(0); + + assertThatThrownBy(() -> testEntity.getCustomField(myCustomField)) + .isExactlyInstanceOf(ObjectNotConvertibleException.class); + } + + // this test verifies backwards compatibility of the API changes + @Test + void entityFieldWithoutConverterStillWorksForGet() + { + mockResponses("\"/Date(1507075200000)/\""); + + final EntityField myCustomField = new EntityField<>(MY_CUSTOM_FIELD); + + final TestEntity TestEntity = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .read(TestEntity.class, ENTITY_SET) + .executeRequest(destination) + .get(0); + + final GregorianCalendar customField = TestEntity.getCustomField(myCustomField); + assertThat(customField).isNotNull(); + } + + @Test + void localDateTimeToCalendarOnSet() + { + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeCalendarConverter()); + + final TestEntity TestEntity = new TestEntity(); + TestEntity.setCustomField(myCustomField, LocalDateTime.of(2017, Month.OCTOBER, 4, 0, 0, 0)); + + final Object myCustomFieldValue = TestEntity.getCustomField(MY_CUSTOM_FIELD); + assertThat(myCustomFieldValue).isInstanceOf(GregorianCalendar.class); + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.set(2017, Calendar.OCTOBER, 4, 0, 0, 0); + assertThat(myCustomFieldValue).isEqualTo(calendar); + } + + @Test + void setsNullOnNullValue() + { + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeCalendarConverter()); + + final TestEntity testEntity = new TestEntity(); + testEntity.setCustomField(myCustomField, null); + + final Object myCustomFieldValue = testEntity.getCustomField(MY_CUSTOM_FIELD); + assertThat(myCustomFieldValue).isNull(); + final LocalDateTime customField = testEntity.getCustomField(myCustomField); + assertThat(customField).isNull(); + } + + @Test + void failsOnConversionErrorOnSet() + { + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeBooleanConverter()); + + final TestEntity testEntity = new TestEntity(); + assertThatThrownBy( + () -> testEntity.setCustomField(myCustomField, LocalDateTime.of(2017, Month.OCTOBER, 4, 0, 0, 0))) + .isExactlyInstanceOf(ObjectNotConvertibleException.class); + } + + // this test verifies backwards compatibility of the API changes + @Test + void entityFieldWithoutConverterStillWorksForSet() + { + final EntityField myCustomField = new EntityField<>(MY_CUSTOM_FIELD); + + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.set(2017, Calendar.OCTOBER, 4, 0, 0, 0); + + final TestEntity testEntity = new TestEntity(); + testEntity.setCustomField(myCustomField, calendar); + } + + @Test + void entityFieldHasNoTypeConverterWhenRetrievedWithoutTypeConverter() + { + final EntityField myCustomField = new EntityField<>(MY_CUSTOM_FIELD); + Assertions.assertThat(myCustomField.getTypeConverter()).isNull(); + } + + @Test + void entityFieldHasTypeConverterWhenRetrievedWithTypeConverter() + { + final EntityField myCustomField = + new EntityField<>(MY_CUSTOM_FIELD, new LocalDateTimeCalendarConverter()); + Assertions.assertThat(myCustomField.getTypeConverter()).isNotNull(); + Assertions.assertThat(myCustomField.getFieldName()).isEqualTo(MY_CUSTOM_FIELD); + } + + private void mockResponses( final String customFieldValue ) + { + final String mockedUrl = ODATA_ENDPOINT_URL + "/" + ENTITY_SET; + stubFor(get(urlEqualTo(mockedUrl)).willReturn(okJson(String.format(RESPONSE, customFieldValue)))); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.java new file mode 100644 index 0000000000..b9a5b0f986 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.java @@ -0,0 +1,368 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5CacheBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestinationProperties; + +import lombok.Getter; +import lombok.SneakyThrows; + +class FluentHelperEtagParsingTest +{ + private static final String SERVICE_PATH = "/service"; + private static final String ENTITY_COLLECTION = "/EntityCollection"; + private static final HttpDestinationProperties DESTINATION = DefaultHttpDestination.builder("http://1").build(); + private static final String FUNCTION_NAME = "FUNCTION_NAME"; + private static final String ETAG_HEAD = "foo"; + private static final String ETAG_BODY = "bar"; + private static final FluentHelperFactory FACTORY = FluentHelperFactory.withServicePath(SERVICE_PATH); + private static final Map HEADERS_NONE = Collections.emptyMap(); + private static final Map HEADERS_WITH_ETAG = Collections.singletonMap("Etag", ETAG_HEAD); + + private HttpClient httpClient; + + @BeforeEach + void setupConnectivity() + { + httpClient = mock(HttpClient.class); + ApacheHttpClient5Accessor + .setHttpClientCache(new ApacheHttpClient5CacheBuilder().durationInMilliseconds(0).build()); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> { + assertThat(dest).isSameAs(DESTINATION); + return httpClient; + }); + } + + @AfterEach + void teardownConnectivity() + { + ApacheHttpClient5Accessor + .setHttpClientCache(new ApacheHttpClient5CacheBuilder().duration(Duration.ofMinutes(5)).build()); + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } + + /** + * ETag via HTTP header. + */ + @SneakyThrows + @Test + void testParseNoEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{d:{}}", HEADERS_NONE)); + + final TestEntity entity = + FACTORY + .readByKey(TestEntity.class, ENTITY_COLLECTION, Collections.singletonMap("key", "val")) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseNoEtagFunctionImport() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(String.format("{d:{%s:{}}}", FUNCTION_NAME), HEADERS_NONE)); + + // GET + TestEntity entity = + FACTORY + .functionSingleGet(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + + // POST + entity = + FACTORY + .functionSinglePost(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseNoEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{d:{}}", HEADERS_NONE)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + FACTORY.update(ENTITY_COLLECTION, requestEntity).executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).isEmpty(); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).isEmpty(); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseHeaderEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{d:{}}", HEADERS_WITH_ETAG)); + + final TestEntity entity = + FACTORY + .readByKey(TestEntity.class, ENTITY_COLLECTION, Collections.singletonMap("key", "val")) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderEtagFunctionImport() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{d:{%s:{}}}", FUNCTION_NAME), HEADERS_WITH_ETAG)); + + // GET + TestEntity entity = + FACTORY + .functionSingleGet(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + + // POST + entity = + FACTORY + .functionSinglePost(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{d:{}}", HEADERS_WITH_ETAG)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + FACTORY.update(ENTITY_COLLECTION, requestEntity).executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * ETag via HTTP payload. + */ + @SneakyThrows + @Test + void testParsePayloadEtagGetAll() + { + final String payload = String.format("{d:{results:[{__metadata:{etag:\"%s\"}}]}}", ETAG_BODY); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(payload, HEADERS_NONE)); + + final List entities = FACTORY.read(TestEntity.class, ENTITY_COLLECTION).executeRequest(DESTINATION); + + assertThat(entities).isNotNull().hasSize(1); + assertThat(entities.get(0).getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{d:{__metadata:{etag:\"%s\"}}}", ETAG_BODY), HEADERS_NONE)); + + final TestEntity entity = + FACTORY + .readByKey(TestEntity.class, ENTITY_COLLECTION, Collections.singletonMap("key", "val")) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagFunctionImport() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn( + response(String.format("{d:{%s:{__metadata:{etag:\"%s\"}}}}", FUNCTION_NAME, ETAG_BODY), HEADERS_NONE)); + + // GET + TestEntity entity = + FACTORY + .functionSingleGet(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + + // POST + entity = + FACTORY + .functionSinglePost(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{d:{__metadata:{etag:\"%s\"}}}", ETAG_BODY), HEADERS_NONE)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + FACTORY.update(ENTITY_COLLECTION, requestEntity).executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_BODY); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_BODY); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * ETag via HTTP header + HTTP payload. + */ + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{d:{__metadata:{etag:\"%s\"}}}", ETAG_BODY), HEADERS_WITH_ETAG)); + + final TestEntity entity = + FACTORY + .readByKey(TestEntity.class, ENTITY_COLLECTION, Collections.singletonMap("key", "val")) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagFunctionImport() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn( + response( + String.format("{d:{%s:{__metadata:{etag:\"%s\"}}}}", FUNCTION_NAME, ETAG_BODY), + HEADERS_WITH_ETAG)); + + // GET + TestEntity entity = + FACTORY + .functionSingleGet(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + + // POST + entity = + FACTORY + .functionSinglePost(Collections.singletonMap("key", "val"), FUNCTION_NAME, TestEntity.class) + .executeRequest(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{d:{__metadata:{etag:\"%s\"}}}", ETAG_BODY), HEADERS_WITH_ETAG)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + FACTORY.update(ENTITY_COLLECTION, requestEntity).executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * HELPER METHODS. + */ + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String entityCollection = ENTITY_COLLECTION; + @Getter + private final Class type = TestEntity.class; + } + + @Nonnull + static ClassicHttpResponse response( @Nonnull final String payload, @Nonnull final Map headers ) + { + final BasicClassicHttpResponse result = new BasicClassicHttpResponse(200, "Ok"); + result.setEntity(new StringEntity(payload, StandardCharsets.UTF_8)); + result.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + headers.forEach(result::setHeader); + return result; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.java new file mode 100644 index 0000000000..3c088d4fa3 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.java @@ -0,0 +1,153 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@WireMockTest +class FunctionImportSingleDeserializationTest +{ + private static final String SERVICE_PATH = "/some/path/service"; + private static final String FUNCTION_NAME = "SomeFunction"; + + @ParameterizedTest + @MethodSource + void testDeserializeSingleResponse( @Nonnull final TestInput testInput, @Nonnull final WireMockRuntimeInfo wm ) + { + stub(testInput); + + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + final Object actualReturnValue = testInput.functionSingleGet().executeSingle(destination); + + if( testInput.expectedReturnValue == null ) { + assertThat(actualReturnValue).isNull(); + } else { + assertThat(actualReturnValue).isEqualTo(testInput.expectedReturnValue); + } + } + + @Nonnull + static Stream> testDeserializeSingleResponse() + { + return Stream + .of( + new TestInput<>( + String.class, + """ + + + + + + """, + "{\"d\":{\"" + + FUNCTION_NAME + + "\":\"" + + "\\n" + + "\\n" + + "\\n" + + "\\n" + + "\\n\"}}"), + new TestInput<>(Boolean.class, true, "{\"d\":{\"" + FUNCTION_NAME + "\":true}}"), + new TestInput<>(Long.class, 42L, "{\"d\":{\"" + FUNCTION_NAME + "\":\"42\"}}"), + new TestInput<>(Void.class, null, null), + new TestInput<>( + TestingEntity.class, + new TestingEntity("id", "text"), + "{\"d\":{\"" + FUNCTION_NAME + "\":{\"id\":\"id\",\"text\":\"text\"}}}")); + } + + private void stub( @Nonnull final TestInput testInput ) + { + stubFor(head(urlPathEqualTo(SERVICE_PATH)).willReturn(ok().withHeader("x-csrf-token", "some-csrf-token"))); + + final ResponseDefinitionBuilder responseBuilder; + if( testInput.serializedReturnValue == null ) { + responseBuilder = noContent(); + } else { + responseBuilder = + ok() + .withBody(testInput.serializedReturnValue) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); + } + + stubFor(get(urlPathEqualTo(SERVICE_PATH + "/" + FUNCTION_NAME)).willReturn(responseBuilder)); + } + + @AllArgsConstructor + @Getter + private static class TestInput + { + @Nonnull + private Class returnValueType; + @Nullable + private T expectedReturnValue; + @Nullable + private String serializedReturnValue; + + @Nonnull + public FluentHelperFunction functionSingleGet() + { + return FluentHelperFactory + .withServicePath(SERVICE_PATH) + .functionSingleGet(Collections.emptyMap(), FUNCTION_NAME, returnValueType); + } + + @Override + public String toString() + { + return "Single Function import with " + returnValueType.getSimpleName() + " return type"; + } + } + + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestingEntity extends VdmEntity + { + @Getter( AccessLevel.PROTECTED ) + private final String entityCollection = "TestingCollection"; + + @Getter + private final Class type = TestingEntity.class; + + @SerializedName( "id" ) + @ODataField( odataName = "id" ) + private String id; + + @SerializedName( "text" ) + @ODataField( odataName = "text" ) + private String text; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.java new file mode 100644 index 0000000000..0d7b721f68 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.java @@ -0,0 +1,260 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.okForJson; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.net.URI; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestinationProperties; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@WireMockTest +class FunctionImportSingleEntityTest +{ + private static final String TEST_SERVICE_PATH = "/some/service/path"; + private static final String TEST_FUNCTION_NAME = "Testing"; + private static final String CSRF = "secret"; + private static final TestingEntity TEST_ENTITY = new TestingEntity("hello", "world"); + + private HttpDestinationProperties destination; + + @BeforeEach + void assignMockedDestination( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + final Map> functionResult = + ImmutableMap.of("d", ImmutableMap.of(TEST_FUNCTION_NAME, TEST_ENTITY)); + stubFor(get(anyUrl()).willReturn(okForJson(functionResult))); + stubFor(head(anyUrl()).willReturn(ok().withHeader("x-csrf-token", CSRF))); + } + + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestingEntity extends VdmEntity + { + @Getter( AccessLevel.PROTECTED ) + private final String entityCollection = "TestingCollection"; + + @Getter + private final Class type = TestingEntity.class; + + @SerializedName( "id" ) + @ODataField( odataName = "id" ) + private String id; + + @SerializedName( "text" ) + @ODataField( odataName = "text" ) + private String text; + } + + private static class SingleEntityFunctionImport + extends + FluentHelperFunction + { + @Getter + private final Map parameters = Maps.newHashMap(); + + @Getter + private final String functionName = TEST_FUNCTION_NAME; + + @Getter + private final Class entityClass = TestingEntity.class; + + public SingleEntityFunctionImport() + { + super(TEST_SERVICE_PATH); + } + + @Nullable + @Override + protected JsonElement refineJsonResponse( @Nullable final JsonElement jsonElement ) + { + return Option.of(jsonElement).toTry().map(o -> o.getAsJsonObject().get(getFunctionName())).getOrNull(); + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return new HttpGet(uri); + } + + @Nullable + @Override + public TestingEntity executeRequest( @Nonnull final Destination destination ) + { + return super.executeSingle(destination); + } + + private void addParameter( final String key, final Object value ) + { + parameters.put(key, value); + } + } + + @Test + void testFunctionParameterWithPlusSign() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("foo", "ba+r"); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify(getRequestedFor(urlEqualTo(TEST_SERVICE_PATH + "/Testing?foo='ba%2Br'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionParameterWithWhitespace() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("foo", "b a+ r "); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify(getRequestedFor(urlEqualTo(TEST_SERVICE_PATH + "/Testing?foo='b%20a%2B%20%20r%20'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionParameterWithQuotes() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("GUID", UUID.fromString("571c74ab-c66a-4d34-ab32-ff16ec4653a5")); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify( + getRequestedFor( + urlEqualTo(TEST_SERVICE_PATH + "/Testing?GUID=guid'571c74ab-c66a-4d34-ab32-ff16ec4653a5'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionParameterWithSpecialCharacters() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("Address", "Unusual? Road #99 with 100%"); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify( + getRequestedFor( + urlEqualTo(TEST_SERVICE_PATH + "/Testing?Address='Unusual%3F%20Road%20%2399%20with%20100%25'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionParameterWithParentheses() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("Address", "Potsdam (Germany)"); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify(getRequestedFor(urlEqualTo(TEST_SERVICE_PATH + "/Testing?Address='Potsdam%20(Germany)'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testCustomJsonResponseParsing() + { + final String jsonResponse = "{\"d\":{\"CUSTOM_OBJECT\":{\"id\":\"hello\",\"text\":\"world\"}}}"; + final MappingBuilder requestMapping = get(urlEqualTo(TEST_SERVICE_PATH + "/Testing")); + stubFor(requestMapping.willReturn(okJson(jsonResponse))); + + final SingleEntityFunctionImport originalFunctionImport = new SingleEntityFunctionImport() + { + @Nonnull + @Override + public String getFunctionName() + { + return "Testing"; + } + + @Override + protected JsonElement refineJsonResponse( @Nullable final JsonElement jsonElement ) + { + return jsonElement.getAsJsonObject().get("CUSTOM_OBJECT"); + } + }; + + final TestingEntity responseEntity = originalFunctionImport.executeRequest(destination); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionParameterWithConventionalValue() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + functionImport.addParameter("Address", "Potsdam"); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify(getRequestedFor(urlEqualTo(TEST_SERVICE_PATH + "/Testing?Address='Potsdam'"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionImportWithoutParameters() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + + final TestingEntity responseEntity = functionImport.executeRequest(destination); + + verify(getRequestedFor(urlEqualTo(TEST_SERVICE_PATH + "/Testing"))); + assertThat(responseEntity).isEqualTo(TEST_ENTITY); + } + + @Test + void testFunctionImportReturnsNullAsResponseBody() + { + final SingleEntityFunctionImport functionImport = new SingleEntityFunctionImport(); + + stubFor(get(urlEqualTo(TEST_SERVICE_PATH + "/Testing")).willReturn(null)); + + assertThatExceptionOfType(ODataException.class) + .isThrownBy(() -> functionImport.executeRequest(destination)) + .withMessageContaining("Unable to read OData 2.0 response."); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.java new file mode 100644 index 0000000000..14a9a3fb5f --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.java @@ -0,0 +1,197 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON; +import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; + +import lombok.SneakyThrows; + +public class HttpResponseEvaluationTest +{ + private static final Destination DESTINATION = DefaultHttpDestination.builder("foo").build(); + + private HttpClient httpClient; + private BasicClassicHttpResponse httpResponse; + private InputStreamEntity httpEntity; + private InputStream inputStream; + + @SneakyThrows + void mockHttpResponse( final ContentType contentType, final String payload ) + { + httpClient = mock(HttpClient.class); + inputStream = spy(new ByteArrayInputStream(payload.getBytes(UTF_8))); + httpEntity = spy(new InputStreamEntity(inputStream, contentType)); + httpResponse = spy(new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK")); + httpResponse.setEntity(httpEntity); + ApacheHttpClient5Accessor.setHttpClientFactory(destination -> httpClient); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(httpResponse); + } + + @AfterEach + void teardown() + { + ApacheHttpClient5Accessor.setHttpClientFactory(null); + ApacheHttpClient5Accessor.setHttpClientCache(null); + } + + @SneakyThrows + @Test + void testCreate() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\": {}}"); + + final ModificationResponse result = + FluentHelperFactory.withServicePath("/path").create(new TestVdmEntity()).executeRequest(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testUpdate() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\": {}}"); + + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setIntegerValue(42); + + final ModificationResponse result = + FluentHelperFactory.withServicePath("/path").update(testEntity).executeRequest(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testDelete() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\": {}}"); + + final ModificationResponse result = + FluentHelperFactory.withServicePath("/path").delete(new TestVdmEntity()).executeRequest(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testReadAll() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\": {\"results\": []}}"); + + final List result = + FluentHelperFactory + .withServicePath("/path") + .read(TestVdmEntity.class, "TestEntitySet") + .executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testReadByKey() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\":{}}"); + + final TestVdmEntity result = + FluentHelperFactory + .withServicePath("/path") + .readByKey(TestVdmEntity.class, "TestEntitySet", Map.of("IntegerValue", 42)) + .executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testFunction() + { + mockHttpResponse(APPLICATION_JSON, "{\"d\": {\"results\": []}}"); + + final List result = + FluentHelperFactory + .withServicePath("/path") + .functionMultipleGet(Map.of("para", "meter"), "functionName", String.class) + .executeRequest(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testReadCount() + { + mockHttpResponse(TEXT_PLAIN, "42"); + + final long result = + FluentHelperFactory + .withServicePath("/path") + .read(TestVdmEntity.class, "TestEntitySet") + .count() + .executeRequest(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(HttpUriRequest.class), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.java new file mode 100644 index 0000000000..c5899778ed --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.java @@ -0,0 +1,175 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V4; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.jupiter.api.Test; + +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odata.client.request.UpdateStrategy; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.ToString; + +class ModificationResponseTest +{ + private static final String SERVICE_PATH = "/service-path"; + + private static final Destination destination = mock(Destination.class); + + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestObject extends VdmEntity + { + @Getter + private final String odataType = "TestObject"; + + @Getter + private final Class type = TestObject.class; + + @ElementName( "foo" ) + private String name; + + @Nonnull + @Override + protected String getEntityCollection() + { + return odataType; + } + } + + @Test + void testEntityResponse() + { + final TestObject inputObject = new TestObject(); + + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + when(request.getProtocol()).thenReturn(ODataProtocol.V4); + + when(request.getServicePath()).thenReturn(SERVICE_PATH); + + final Header[] responseHeaders = { new BasicHeader("fizz", "buzz"), new BasicHeader("fizz", "fuzz, bizz=1") }; + + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(responseHeaders).when(response).getHeaders(); + doReturn(responseHeaders).when(response).getHeaders("ETag"); + doReturn(new StringEntity("{\"foo\":\"bar\"}", UTF_8)).when(response).getEntity(); + doReturn(HttpStatus.SC_OK).when(response).getCode(); + + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final ModificationResponse modification = ModificationResponse.of(result, inputObject, destination); + + assertThat(modification).isNotNull(); + assertThat(modification.getResponseStatusCode()).isEqualTo(HttpStatus.SC_OK); + assertThat(modification.getRequestEntity()).isSameAs(inputObject); + + assertThat(modification.getResponseEntity().get()).isNotSameAs(inputObject); + assertThat(modification.getResponseEntity().get()).isEqualTo(new TestObject("bar")); + assertThat(modification.getModifiedEntity()).isEqualTo(new TestObject("bar")); + + assertThat(modification.getModifiedEntity().getDestinationForFetch()).isSameAs(destination); + assertThat(modification.getModifiedEntity().getServicePathForFetch()).isEqualTo(SERVICE_PATH); + + assertThat(modification.getResponseHeaders()).containsOnlyKeys("fizz"); + assertThat(modification.getResponseHeaders().get("fizz")).containsExactly("buzz", "fuzz, bizz=1"); + } + + @Test + void testEmptyResponse() + { + final TestObject inputObject = new TestObject(); + + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + when(request.getProtocol()).thenReturn(ODataProtocol.V4); + + when(request.getServicePath()).thenReturn(SERVICE_PATH); + + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(new Header[0]).when(response).getHeaders(); + doReturn(new Header[0]).when(response).getHeaders("ETag"); + doReturn(new StringEntity("", UTF_8)).when(response).getEntity(); + doReturn(HttpStatus.SC_NO_CONTENT).when(response).getCode(); + + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final ModificationResponse modification = ModificationResponse.of(result, inputObject, destination); + + assertThat(modification).isNotNull(); + assertThat(modification.getResponseStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + assertThat(modification.getRequestEntity()).isSameAs(inputObject); + assertThat(modification.getModifiedEntity()).isNotSameAs(inputObject); + assertThat(modification.getModifiedEntity().getDestinationForFetch()).isSameAs(destination); + assertThat(modification.getModifiedEntity().getServicePathForFetch()).isEqualTo(SERVICE_PATH); + assertThat(modification.getModifiedEntity()).isEqualTo(inputObject); + assertThat(modification.getResponseHeaders()).isEmpty(); + } + + @SneakyThrows + @Test + void testResponseIsOnlyEvaluatedOnce() + { + final TestObject inputObject = new TestObject(); + + final ClassicHttpResponse response = spy(new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK")); + response.setHeaders(new Header[0]); + response.setEntity(new StringEntity("{\"foo\":\"bar\"}", UTF_8)); + + final ODataEntityKey key = ODataEntityKey.of(Map.of("id", 42), V4); + final ODataRequestUpdate request = + new ODataRequestUpdate("service/path", "EntitySet", key, "{}", UpdateStrategy.REPLACE_WITH_PUT, null, V4); + + final HttpClient httpClient = mock(HttpClient.class); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response); + + final ODataRequestResultGeneric result = request.execute(httpClient); + final ModificationResponse modification = ModificationResponse.of(result, inputObject, destination); + + modification.getResponseEntity(); + final Option responseEntity = modification.getResponseEntity(); + assertThat(responseEntity).isNotNull(); + + modification.getModifiedEntity(); + final TestObject modifiedEntity = modification.getModifiedEntity(); + assertThat(modifiedEntity).isNotNull(); + + verify(response, times(1)).getEntity(); + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.java new file mode 100644 index 0000000000..72a6350462 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.java @@ -0,0 +1,436 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.Lists; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchChangeSetFluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchFluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchResponse; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchResponseChangeSet; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import io.vavr.control.Try; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.With; + +@WireMockTest +class ODataBatchRequestTest +{ + private static final String ODATA_SERVICE_PATH = "/service/path"; + + private static String multipartCreatedEntity( final String key, final String description ) + { + final String url = String.format("https://127.0.0.1/service/path/A_TestEntity('%s')", key); + final String metadata = String.format("{\"id\":\"%s\",\"uri\":\"%s\",\"type\":\"A_TestEntityType\"}", url, url); + return "Content-Type: application/http\r\n" + + "Content-Length: 2811\r\n" + + "content-transfer-encoding: binary\r\n" + + "\r\n" + + "HTTP/1.1 201 Created\r\n" + + "Content-Type: application/json\r\n" + + "Content-Length: 2553\r\n" + + ("location: " + url + ")\r\n") + + "dataserviceversion: 2.0\r\n" + + "\r\n" + + "{" + + ("\"d\":{\"__metadata\":" + metadata + ",") + + ("\"Name\":\"" + key + "\",") + + ("\"GeneratedProperty\":\"" + UUID.randomUUID() + "'\",") + + ("\"Description\":\"" + description + "\"") + + "}}\r\n"; + } + + private static String multipartNoContent() + { + return """ + Content-Type: application/http + Content-Length: 71 + content-transfer-encoding: binary + + HTTP/1.1 204 No Content + Content-Length: 0 + dataserviceversion: 2.0 + """; + } + + private static final String SINGLE_CHANGESET_RESPONSE_MULTIPART = + "--FB687BFCC8917ABB09014537111216650\r\n" + + "Content-Type: multipart/mixed; boundary=FB687BFCC8917ABB09014537111216651\r\n" + + "Content-Length: 5921\r\n" // ignored + + "\r\n" + + "--FB687BFCC8917ABB09014537111216651\r\n" + + multipartCreatedEntity("501", "Same description") + + "--FB687BFCC8917ABB09014537111216651\r\n" + + multipartCreatedEntity("502", "Same description") + + "--FB687BFCC8917ABB09014537111216651--\r\n" + + "\r\n" + + "--FB687BFCC8917ABB09014537111216650--"; + + private static final String TWO_CHANGESETS_RESPONSE_MULTIPART = + "--9955BD2AAB53BE8D34D8913DDB06697B0\r\n" + + "Content-Type: multipart/mixed; boundary=9955BD2AAB53BE8D34D8913DDB06697B1\r\n" + + "Content-Length: 2980\r\n" // ignored + + "\r\n" + + "--9955BD2AAB53BE8D34D8913DDB06697B1\r\n" + + multipartCreatedEntity("503", "Alternate description") + + "--9955BD2AAB53BE8D34D8913DDB06697B1--\r\n" + + "\r\n" + + "--9955BD2AAB53BE8D34D8913DDB06697B0\r\n" + + "Content-Type: multipart/mixed; boundary=9955BD2AAB53BE8D34D8913DDB06697B1\r\n" + + "Content-Length: 2980\r\n" // ignored + + "\r\n" + + "--9955BD2AAB53BE8D34D8913DDB06697B1\r\n" + + multipartCreatedEntity("504", "Strange description") + + "--9955BD2AAB53BE8D34D8913DDB06697B1--\r\n" + + "\r\n" + + "--9955BD2AAB53BE8D34D8913DDB06697B0--"; + + private static final String SINGLE_UPDATE_DELETE_CHANGESET_RESPONSE_MULTIPART = + "--4729730C52B1E9D05AAAB969F2FEE6970\r\n" + + "Content-Type: multipart/mixed; boundary=4729730C52B1E9D05AAAB969F2FEE6971\r\n" + + "Content-Length: 437\r\n" // ignored + + "\r\n" + + "--4729730C52B1E9D05AAAB969F2FEE6971\r\n" + + multipartNoContent() + + "\r\n" + + "\r\n" + + "--4729730C52B1E9D05AAAB969F2FEE6971\r\n" + + multipartNoContent() + + "\r\n" + + "\r\n" + + "--4729730C52B1E9D05AAAB969F2FEE6971--\r\n" + + "\r\n" + + "--4729730C52B1E9D05AAAB969F2FEE6970--"; + + private HttpDestination destination; + + @BeforeEach + void before( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + stubFor(head(urlEqualTo(ODATA_SERVICE_PATH)).willReturn(ok())); + } + + @Test + void testTwoCreatesInOneChangeSet() + { + // prepare mocked data + final ResponseDefinitionBuilder response = + okForContentType( + "multipart/mixed; boundary=FB687BFCC8917ABB09014537111216650", + SINGLE_CHANGESET_RESPONSE_MULTIPART); + stubFor(post(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")).willReturn(response)); + + final TestEntity address1 = new TestEntity(); + address1.setDescription("Same description"); + + final TestEntity address2 = new TestEntity(); + address2.setDescription("Same description"); + + final BatchResponse result = + new TestBatch() + .beginChangeSet() + .createTestEntity(address1) + .createTestEntity(address2) + .endChangeSet() + .executeRequest(destination); + + final Try changeSet = result.get(0); + assertThat(changeSet.isSuccess()).isTrue(); + + final List> createdEntities = changeSet.get().getCreatedEntities(); + assertResponse(createdEntities, 2, address1); + } + + @Test + void testTwoCreatesInMultipleChangeSets() + { + // prepare mocked data + final ResponseDefinitionBuilder response = + okForContentType( + "multipart/mixed; boundary=9955BD2AAB53BE8D34D8913DDB06697B0", + TWO_CHANGESETS_RESPONSE_MULTIPART); + stubFor(post(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")).willReturn(response)); + + final TestEntity address1 = new TestEntity(); + address1.setDescription("Alternate description"); + + final TestEntity address2 = new TestEntity(); + address2.setDescription("Strange description"); + + final BatchResponse result = + new TestBatch() + + .beginChangeSet() + .createTestEntity(address1) + .endChangeSet() + + .beginChangeSet() + .createTestEntity(address2) + .endChangeSet() + + .executeRequest(destination); + + assertThat(result.get(0)).isNotEmpty(); + assertResponse(result.get(0).get().getCreatedEntities(), 1, address1); + + assertThat(result.get(1)).isNotEmpty(); + assertResponse(result.get(1).get().getCreatedEntities(), 1, address2); + } + + @Test + void testUpdateDeleteOperationInOneChangeSet() + { + final String concreteNameEndpoint = "A_TestEntity(%27Test%27)"; + + final TestEntity address = new TestEntity().withName("Test"); + + // prepare mocked data + final ResponseDefinitionBuilder response = + okForContentType( + "multipart/mixed; boundary=4729730C52B1E9D05AAAB969F2FEE6970", + SINGLE_UPDATE_DELETE_CHANGESET_RESPONSE_MULTIPART); + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")) + .withRequestBody(containing("PATCH " + concreteNameEndpoint)) + .withRequestBody(containing("DELETE " + concreteNameEndpoint)) + .willReturn(response)); + + // change entity + address.setDescription("Alternative Description"); + + final BatchResponse result = + new TestBatch() + .beginChangeSet() + .updateTestEntity(address) + .deleteTestEntity(address) + .endChangeSet() + .executeRequest(destination); + + final Try changeSet = result.get(0); + assertThat(changeSet.isSuccess()).isTrue(); + } + + @Test + void testUpdateWithPatchRetainingNullValues() + { + final String concreteNameEndpoint = "A_TestEntity(%27Test%27)"; + + final TestEntity address = new TestEntity().withName("Test").withDescription("Existing description"); + + // prepare mocked data + final ResponseDefinitionBuilder response = + okForContentType( + "multipart/mixed; boundary=4729730C52B1E9D05AAAB969F2FEE6970", + SINGLE_UPDATE_DELETE_CHANGESET_RESPONSE_MULTIPART); + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")) + .withRequestBody(containing("PATCH " + concreteNameEndpoint)) + .willReturn(response)); + + // change entity + address.setDescription(null); + final BatchResponse result = + new TestBatch().beginChangeSet().updateTestEntity(address).endChangeSet().executeRequest(destination); + + verify( + postRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")) + .withRequestBody(containing("\"Description\":" + null))); + final Try changeSet = result.get(0); + assertThat(changeSet.isSuccess()).isTrue(); + } + + /* + * Assertion helpers + */ + private void assertResponse( List> createdEntities, int expectedSize, TestEntity referenceEntity ) + { + // general assertion: two created entities not equal to each other or to the input instance + assertThat(createdEntities).hasSize(expectedSize).doesNotHaveDuplicates().doesNotContain((VdmEntity) null); + assertThat(createdEntities).allMatch(TestEntity.class::isInstance).doesNotContain(referenceEntity); + + // cast items to TestEntity + final List createdTestEntities = + createdEntities.stream().map(TestEntity.class::cast).collect(Collectors.toList()); + + // check for auto generated fields ID and UUID, must not be similar or equal to input + assertThat(createdTestEntities) + .extracting(TestEntity::getName) + .isNotEmpty() + .doesNotHaveDuplicates() + .doesNotContain(referenceEntity.getName(), "", null); + + assertThat(createdTestEntities) + .extracting(TestEntity::getGeneratedProperty) + .isNotEmpty() + .doesNotHaveDuplicates() + .doesNotContain(referenceEntity.getGeneratedProperty()); + + // check for valid input data + for( final Function handler : Lists + .> newArrayList(TestEntity::getDescription) ) { + assertThat(createdTestEntities).extracting(handler).containsOnly(handler.apply(referenceEntity)); + } + } + + @Test + void testEncodingInBatchedUpdate() + { + final ResponseDefinitionBuilder response = + okForContentType( + "multipart/mixed; boundary=FB687BFCC8917ABB09014537111216650", + SINGLE_CHANGESET_RESPONSE_MULTIPART); + stubFor( + post(urlEqualTo(ODATA_SERVICE_PATH + "/$batch")) + .withRequestBody(containing("Müller Straße")) + .willReturn(response)); + + final TestEntity address = new TestEntity().withName("Müller Straße"); + address.setDescription("Müller Straße"); + + new TestBatch().beginChangeSet().updateTestEntity(address).endChangeSet().executeRequest(destination); + + verify(1, postRequestedFor(urlEqualTo(ODATA_SERVICE_PATH + "/$batch"))); + verify(1, headRequestedFor(anyUrl()).withHeader("x-csrf-token", equalTo("fetch"))); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @EqualsAndHashCode( callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = "A_TestEntity"; + private final Class type = TestEntity.class; + + @SerializedName( "Name" ) + @JsonProperty( "Name" ) + @ODataField( odataName = "Name" ) + @With + private String name; + + @SerializedName( "Description" ) + @JsonProperty( "Description" ) + @ODataField( odataName = "Description" ) + @With + private String description; + + @SerializedName( "GeneratedProperty" ) + @JsonProperty( "GeneratedProperty" ) + @ODataField( odataName = "GeneratedProperty" ) + private String generatedProperty; + + @Nonnull + @Override + protected Map getKey() + { + return Collections.singletonMap("Name", getName()); + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map result = new LinkedHashMap<>(); + result.put("Name", getName()); + result.put("Description", getDescription()); + return result; + } + + public void setDescription( final String description ) + { + rememberChangedField("Description", this.description); + this.description = description; + } + } + + @Getter + public static class TestBatch extends BatchFluentHelperBasic + { + private final String servicePathForBatchRequest = ODATA_SERVICE_PATH; + + @Nonnull + @Override + protected TestBatch getThis() + { + return this; + } + + @Nonnull + @Override + public TestBatchChangeset beginChangeSet() + { + return new TestBatchChangeset(this); + } + } + + public static class TestBatchChangeset extends BatchChangeSetFluentHelperBasic + { + public TestBatchChangeset( final TestBatch parent ) + { + super(parent, parent); + } + + @Nonnull + @Override + protected TestBatchChangeset getThis() + { + return this; + } + + public TestBatchChangeset updateTestEntity( final TestEntity entity ) + { + return super.addRequestUpdate(FluentHelperFactory.withServicePath(ODATA_SERVICE_PATH)::update, entity); + } + + public TestBatchChangeset createTestEntity( final TestEntity entity ) + { + return super.addRequestCreate(FluentHelperFactory.withServicePath(ODATA_SERVICE_PATH)::create, entity); + } + + public TestBatchChangeset deleteTestEntity( final TestEntity entity ) + { + return super.addRequestDelete(FluentHelperFactory.withServicePath(ODATA_SERVICE_PATH)::delete, entity); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.java new file mode 100644 index 0000000000..b25d5fcd0f --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.java @@ -0,0 +1,118 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class ODataChangedFieldsTest +{ + @Test + void testEmpty() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + @Test + void testBuilder() + { + final TestVdmEntity testEntity = TestVdmEntity.builder().stringValue("Foo").build(); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + @Test + void testAccessor() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setStringValue("Foo"); + assertThat(testEntity.getChangedFields()).containsEntry("StringValue", "Foo"); + } + + @Test + void testAccessorNull() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setDecimalValue(null); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + @Test + void testCustomFields() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setCustomField("BarValue", "Foo"); + assertThat(testEntity.getChangedFields()).containsEntry("BarValue", "Foo"); + } + + @Test + void testCustomFieldsNull() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setCustomField("BarValue", null); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + @Test + void testRevertingValue() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + testEntity.setIntegerValue(9000); + assertThat(testEntity.getChangedFields()).containsEntry("IntegerValue", 9000); + + testEntity.setIntegerValue(null); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + @Test + void testComplexValue() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + final TestVdmComplex complex1 = TestVdmComplex.builder().someValue("Foo").build(); + final TestVdmComplex complex2 = new TestVdmComplex(); + complex2.setSomeValue("Bar"); + + testEntity.setComplexValue(complex1); + assertThat(testEntity.getChangedFields()).containsOnly(entry("ComplexValue", complex1)); + + testEntity.setComplexValue(complex2); + assertThat(testEntity.getChangedFields()).containsOnly(entry("ComplexValue", complex2)); + } + + @Disabled( "Not yet implemented. See CLOUDECOSYSTEM-9065" ) + @Test + void testInnerComplexValue() + { + final TestVdmComplex complex = TestVdmComplex.builder().someValue("Tic").build(); + final TestVdmEntity testEntity = TestVdmEntity.builder().complexValue(complex).build(); + assertThat(testEntity.getChangedFields()).isEmpty(); + + complex.setSomeValue("Tac"); + assertThat(testEntity.getChangedFields()).containsOnly(entry("ComplexValue", complex)); + } + + /** + * Updating changes on related entities in navigation properties are not supported in OData V2 (aka Deep Update). + */ + @Test + void testNavigationPropertyToOne() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + final TestVdmEntity parent = TestVdmEntity.builder().stringValue("Foo").build(); + testEntity.setToParent(parent); + assertThat(testEntity.getChangedFields()).isEmpty(); + } + + /** + * Updating changes on related entities in navigation properties are not supported in OData V2 (aka Deep Update). + */ + @Test + void testNavigationPropertyToMany() + { + final TestVdmEntity testEntity = new TestVdmEntity(); + final TestVdmEntity child1 = TestVdmEntity.builder().stringValue("Foo").build(); + testEntity.addToChildren(child1); + assertThat(testEntity.getChangedFields()).isEmpty(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.java new file mode 100644 index 0000000000..681b6da9ea --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.java @@ -0,0 +1,104 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; + +class ODataEntitySerializerTest +{ + @Test + void testSerializeEntityForCreate() + { + final TestVdmEntity entity = TestVdmEntity.builder().stringValue("string").booleanValue(false).build(); + entity.setIntegerValue(42); + + final String payload = ODataEntitySerializer.serializeEntityForCreate(entity); + assertThat(payload).isEqualTo("{\"IntegerValue\":42,\"StringValue\":\"string\",\"BooleanValue\":false}"); + } + + @Test + void testSerializeEntityForUpdatePut() + { + final TestVdmEntity entity = TestVdmEntity.builder().stringValue("string").booleanValue(false).build(); + entity.setIntegerValue(42); + + final String payload = ODataEntitySerializer.serializeEntityForUpdatePut(entity, null); + assertThat(payload) + .isEqualTo( + "{\"IntegerValue\":42,\"GuidValue\":null,\"StringValue\":\"string\",\"OffsetDateTimeValue\":null,\"to_Parent\":null,\"to_Children\":null,\"DecimalValue\":null,\"DoubleValue\":null,\"LocalTimeValue\":null,\"LocalDateTimeValue\":null,\"BooleanValue\":false,\"ComplexValue\":null}"); + } + + @Test + void testSerializeEntityForUpdatePutWithExcludedFields() + { + final TestVdmEntity entity = TestVdmEntity.builder().stringValue("NewString").booleanValue(true).build(); + entity.setIntegerValue(45); + + final List fieldsToExclude = + Arrays.asList(FieldReference.of("DoubleValue"), FieldReference.of("ComplexValue")); + final String payload = ODataEntitySerializer.serializeEntityForUpdatePut(entity, fieldsToExclude); + assertThat(payload) + .isEqualTo( + "{\"IntegerValue\":45,\"GuidValue\":null,\"StringValue\":\"NewString\",\"OffsetDateTimeValue\":null,\"to_Parent\":null,\"to_Children\":null,\"DecimalValue\":null,\"LocalTimeValue\":null,\"LocalDateTimeValue\":null,\"BooleanValue\":true}"); + } + + @Test + void testSerializeEntityForUpdatePatch() + { + final TestVdmEntity entity = TestVdmEntity.builder().stringValue("string").booleanValue(false).build(); + entity.setIntegerValue(42); + + final Collection fields = Arrays.asList(FieldReference.of("a"), FieldReference.of("b")); + final String payload = ODataEntitySerializer.serializeEntityForUpdatePatchShallow(entity, fields); + assertThat(payload).isEqualTo("{\"a\":null,\"b\":null,\"IntegerValue\":42}"); + } + + @Test + void testSerializeEntityForUpdatePatchNested() + { + final TestVdmComplex grandchildComplex = TestVdmComplex.builder().someValue("initialGrandchildValue").build(); + final TestVdmComplex childComplex = + TestVdmComplex.builder().someValue("initialChildValue").complexValue(grandchildComplex).build(); + final TestVdmEntity rootEntity = + TestVdmEntity + .builder() + .stringValue("initialRootValue") + .booleanValue(false) + .complexValue(childComplex) + .build(); + + rootEntity.setStringValue("newRootValue"); + grandchildComplex.setSomeValue("newGrandchildValue"); + + final Collection additionalFields = Arrays.asList(FieldReference.of("customField")); + + final String fullPayload = + ODataEntitySerializer.serializeEntityForUpdatePatchRecursiveFull(rootEntity, additionalFields); + assertThat(fullPayload) + .isEqualTo( + "{\"StringValue\":\"newRootValue\",\"customField\":null,\"ComplexValue\":{\"SomeValue\":\"initialChildValue\",\"OtherValue\":null,\"ComplexValue\":{\"SomeValue\":\"newGrandchildValue\",\"OtherValue\":null,\"ComplexValue\":null}}}"); + + final String deltaPayload = + ODataEntitySerializer.serializeEntityForUpdatePatchRecursiveDelta(rootEntity, additionalFields); + assertThat(deltaPayload) + .isEqualTo( + "{\"customField\":null,\"ComplexValue\":{\"ComplexValue\":{\"SomeValue\":\"newGrandchildValue\"}},\"StringValue\":\"newRootValue\"}"); + + final TestVdmComplex siblingGrandchildComplex = + TestVdmComplex.builder().someValue("newSiblingGrandchildValue").build(); + childComplex.setComplexValue(siblingGrandchildComplex); + + final String siblingDeltaPayload = + ODataEntitySerializer.serializeEntityForUpdatePatchRecursiveDelta(rootEntity, additionalFields); + assertThat(siblingDeltaPayload) + .isEqualTo( + "{\"customField\":null,\"ComplexValue\":{\"ComplexValue\":{\"SomeValue\":\"newSiblingGrandchildValue\",\"OtherValue\":null,\"ComplexValue\":null}},\"StringValue\":\"newRootValue\"}"); + } + +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.java new file mode 100644 index 0000000000..69c59c59a2 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.java @@ -0,0 +1,111 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +class ODataFilterTest +{ + private static final EntityField STRING_FIELD = new EntityField<>("field"); + private static final EntityField ZONED_DATE_TIME_FIELD = new EntityField<>("field"); + private static final EntityField LOCAL_DATE_TIME_FIELD = new EntityField<>("field"); + private static final EntityField LOCAL_TIME_FIELD = new EntityField<>("field"); + private static final EntityField INT_FIELD = new EntityField<>("field"); + + @ParameterizedTest + @EnumSource( TestInput.class ) + void testFilterExpression( @Nonnull final TestInput testInput ) + { + final ValueBoolean clientExpression = testInput.helper.getDelegateExpressionWithoutOuterParentheses(); + assertThat(clientExpression.getExpression(ODataProtocol.V2)).isEqualTo(testInput.expectedFilterString); + + final ODataRequestRead request = + FluentHelperFactory + .withServicePath("/foo/bar") + .read(TestEntity.class, "foo") + .filter(testInput.helper) + .toRequest(); + assertThat(request.getRelativeUri()).hasQuery("$filter=" + testInput.expectedFilterString); + } + + @AllArgsConstructor + enum TestInput + { + STRING_EQUALITY(STRING_FIELD.eq("va'l''ue"), "field eq 'va''l''''ue'"), + STRING_SUBSTRING(STRING_FIELD.substringOf("foo"), "substringof('foo',field)"), + STRING_ENDS_WITH(STRING_FIELD.endsWith("foo"), "endswith(field,'foo')"), + STRING_STARTS_WITH(STRING_FIELD.startsWith("foo"), "startswith(field,'foo')"), + STRING_DISJUNCTION( + STRING_FIELD.startsWith("foo").or(STRING_FIELD.ne("bar")), + "startswith(field,'foo') or (field ne 'bar')"), + STRING_NEGATION(STRING_FIELD.endsWith("foo").not(), "not endswith(field,'foo')"), + STRING_COMPLEX_EXPRESSION( + STRING_FIELD + .substringOf("bar") + .and(STRING_FIELD.endsWith("foo")) + .or(STRING_FIELD.startsWith("foobar").not()) + .not(), + "not ((substringof('bar',field) and endswith(field,'foo')) or (not startswith(field,'foobar')))"), + ZONED_DATE_TIME( + ZONED_DATE_TIME_FIELD + .eq(ZonedDateTime.of(LocalDate.of(2001, 1, 1), LocalTime.of(20, 15), ZoneId.of("UTC"))), + "field eq datetimeoffset'2001-01-01T20:15:00Z'"), + LOCAL_DATE_TIME( + LOCAL_DATE_TIME_FIELD.eq(LocalDateTime.of(LocalDate.of(2001, 1, 1), LocalTime.of(20, 15))), + "field eq datetime'2001-01-01T20:15:00'"), + LOCAL_TIME(LOCAL_TIME_FIELD.eq(LocalTime.of(20, 15)), "field eq time'PT20H15M'"), + INT_LOGICAL_OPERATORS( + INT_FIELD.eq(1).and(ExpressionFluentHelper.not(INT_FIELD.ne(2))).or(INT_FIELD.eq(3)).not(), + "not (((field eq 1) and (not (field ne 2))) or (field eq 3))"), + INT_RELATIVE_OPERATORS( + INT_FIELD.le(1).and(INT_FIELD.ge(2)).and(INT_FIELD.lt(3).and(INT_FIELD.gt(4))), + "((field le 1) and (field ge 2)) and ((field lt 3) and (field gt 4))"), + INT_EQUALITY(INT_FIELD.eq(1), "field eq 1"), + INT_INEQUALITY(INT_FIELD.ne(1), "field ne 1"), + INT_GREATER_THAN(INT_FIELD.gt(1), "field gt 1"), + INT_GREATER_THAN_OR_EQUAL(INT_FIELD.ge(1), "field ge 1"), + INT_LESS_THAN(INT_FIELD.lt(1), "field lt 1"), + INT_LESS_THAN_OR_EQUAL(INT_FIELD.le(1), "field le 1"), + EQUAL_NULL(INT_FIELD.eq(null), "field eq null"), + EQUALNULL(INT_FIELD.eqNull(), "field eq null"), + NOT_EQUAL_NULL(INT_FIELD.ne(null), "field ne null"), + NOT_EQUALNULL(INT_FIELD.neNull(), "field ne null"), + GREATER_THAN_NULL(INT_FIELD.gt(null), "field gt null"), + GREATER_THAN_OR_EQUAL_NULL(INT_FIELD.ge(null), "field ge null"), + LESS_THAN_NULL(INT_FIELD.lt(null), "field lt null"), + LESS_THAN_OR_EQUAL_NULL(INT_FIELD.le(null), "field le null"),; + + @Nonnull + private final ExpressionFluentHelper helper; + @Nonnull + private final String expectedFilterString; + } + + private static class TestEntity extends VdmEntity + { + @Getter + final String entityCollection = null; + + @Getter + private final String defaultServicePath = "/"; + + @Getter + private final Class type = TestEntity.class; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java new file mode 100644 index 0000000000..07e1810684 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java @@ -0,0 +1,222 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.github.tomakehurst.wiremock.matching.UrlPathPattern; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; + +@WireMockTest +class ODataHeaderTest +{ + private static final String DEFAULT_SERVICE_PATH = new TestVdmEntity().getDefaultServicePath(); + private static final String ENTITY_COLLECTION_PATH = new TestVdmEntity().getEntityCollection(); + private static final UrlPathPattern GET_ALL = urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ENTITY_COLLECTION_PATH); + private static final UrlPathPattern CREATE = urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ENTITY_COLLECTION_PATH); + private static final UrlPathPattern UPDATE = + urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ENTITY_COLLECTION_PATH + "(123)"); + private static final UrlPathPattern DELETE = UPDATE; + private static final UrlPathPattern CSRF = urlPathEqualTo(DEFAULT_SERVICE_PATH + "/"); + + private DefaultHttpDestination destination; + private TestVdmEntity entity; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + stubFor(get(GET_ALL).willReturn(okJson("{\"d\":{\"results\":[]}}"))); + stubFor(patch(UPDATE).willReturn(okJson("{\"d\":{}"))); + stubFor(post(CREATE).willReturn(okJson("{\"d\":{}"))); + stubFor(delete(DELETE).willReturn(ok())); + stubFor( + head(CSRF).withHeader("x-csrf-token", equalTo("fetch")).willReturn(ok().withHeader("x-csrf-token", "abc"))); + + entity = TestVdmEntity.builder().integerValue(123).build(); + } + + // Test for implicitly missing CSRF header token and ETag header when GETTING an entity + @Test + void testNonExistingHeadersForGetDefault() + { + new TestEntityReadFluentHelper().executeRequest(destination); + verify(0, headRequestedFor(CSRF)); + verify(getRequestedFor(GET_ALL).withoutHeader("If-Match").withoutHeader("x-csrf-token")); + } + + // Test for CSRF token header and implicitly without ETag header when UPDATING an entity + @Test + void testUpdateDefaultHeader() + { + new TestEntityUpdateFluentHelper(entity).executeRequest(destination); + verify(headRequestedFor(CSRF).withHeader("x-csrf-token", equalTo("fetch"))); + verify(patchRequestedFor(UPDATE).withHeader("x-csrf-token", equalTo("abc")).withoutHeader("If-Match")); + } + + // Test for CSRF token header and explicit ETag header when UPDATING an entity + @Test + void testUpdateSpecificVersionVersionHeader() + { + entity.setVersionIdentifier("ver"); + new TestEntityUpdateFluentHelper(entity).executeRequest(destination); + verify(headRequestedFor(CSRF).withHeader("x-csrf-token", equalTo("fetch"))); + + verify( + patchRequestedFor(UPDATE) + .withHeader("x-csrf-token", equalTo("abc")) + .withHeader("If-Match", equalTo("ver"))); + } + + // Test for CSRF token header and explicit ETag-wildcard header when UPDATING an entity + @Test + void testUpdateWildcardVersionVersionHeader() + { + new TestEntityUpdateFluentHelper(entity).matchAnyVersionIdentifier().executeRequest(destination); + verify(headRequestedFor(CSRF).withHeader("x-csrf-token", equalTo("fetch"))); + + verify( + patchRequestedFor(UPDATE).withHeader("x-csrf-token", equalTo("abc")).withHeader("If-Match", equalTo("*"))); + } + + // Test for CSRF token header, explicitly without ETag header when UPDATING an entity + @Test + void testUpdateWithoutVersionHeader() + { + new TestEntityUpdateFluentHelper(entity).disableVersionIdentifier().executeRequest(destination); + verify(headRequestedFor(CSRF).withHeader("x-csrf-token", equalTo("fetch"))); + verify(patchRequestedFor(UPDATE).withHeader("x-csrf-token", equalTo("abc")).withoutHeader("If-Match")); + } + + // Test for custom header, implicitly without CSRF token header or ETag header when GETTING an entity + @Test + void testGetCustomHeaderHeader() + { + new TestEntityReadFluentHelper() + .withHeader("Authentication", "yes") + .withHeader("Cookie", "tasty") + .executeRequest(destination); + verify(0, headRequestedFor(CSRF)); + + verify( + getRequestedFor(GET_ALL) + .withHeader("Authentication", equalTo("yes")) + .withHeader("Cookie", equalTo("tasty")) + .withoutHeader("x-csrf-token") + .withoutHeader("If-Match")); + } + + // Test for CSRF token header, explicitly with custom headers when UPDATING an entity + @Test + void testUpdateCustomHeader() + { + new TestEntityUpdateFluentHelper(entity).withHeader("Authentication", "yes").executeRequest(destination); + + // Note: with Apache HttpClient 5 the CSRF token HEAD request is issued by the client-level + // CsrfTokenInterceptor, which does not propagate the request's custom headers. + verify(headRequestedFor(CSRF).withHeader("x-csrf-token", equalTo("fetch"))); + + verify( + patchRequestedFor(UPDATE) + .withHeader("Authentication", equalTo("yes")) + .withHeader("x-csrf-token", equalTo("abc"))); + } + + // fluent helpers + + private static class TestEntityDeleteFluentHelper + extends + FluentHelperDelete + { + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + private TestEntityDeleteFluentHelper( @Nonnull final TestVdmEntity entity ) + { + super(DEFAULT_SERVICE_PATH, entity.getEntityCollection()); + this.entity = entity; + } + + @Override + protected TestVdmEntity getEntity() + { + return entity; + } + } + + private static class TestEntityUpdateFluentHelper + extends + FluentHelperUpdate + { + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + private TestEntityUpdateFluentHelper( @Nonnull final TestVdmEntity entity ) + { + super(DEFAULT_SERVICE_PATH, entity.getEntityCollection()); + this.entity = entity; + } + + @Override + protected TestVdmEntity getEntity() + { + return entity; + } + } + + private static class TestEntityCreateFluentHelper + extends + FluentHelperCreate + { + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + private TestEntityCreateFluentHelper( @Nonnull final TestVdmEntity entity ) + { + super(DEFAULT_SERVICE_PATH, entity.getEntityCollection()); + this.entity = entity; + } + + @Override + protected TestVdmEntity getEntity() + { + return entity; + } + } + + private static class TestEntityReadFluentHelper + extends + FluentHelperRead + { + @SuppressWarnings( "deprecation" ) + private TestEntityReadFluentHelper() + { + super(DEFAULT_SERVICE_PATH, TestVdmEntity.builder().build().getEntityCollection()); + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return TestVdmEntity.class; + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.java new file mode 100644 index 0000000000..d81be99c68 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.java @@ -0,0 +1,215 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +class ODataSelectExpandTest +{ + @Test + void testSelectExpand() + { + final FluentHelperRead entityRead = + FluentHelperFactory.withServicePath("service/path/").read(TestEntity.class, "TestEntity"); + entityRead.select(TestEntity.FOO, TestEntity.TO_PARENT, TestEntity.TO_CHILDREN.select(TestEntity.BAR)); + + final ODataRequestRead requestRead = entityRead.toRequest(); + + assertThat(requestRead.getQueryString()) + .isEqualTo("$select=Foo,to_Parent/*,to_Children/Bar&$expand=to_Parent,to_Children"); + + assertThat(requestRead.getRelativeUri()) + .hasPath("/service/path/TestEntity") + .hasQuery("$select=Foo,to_Parent/*,to_Children/Bar&$expand=to_Parent,to_Children"); + } + + @Test + void testSelectSome() + { + final FluentHelperRead entityRead = + FluentHelperFactory.withServicePath("service/path/").read(TestEntity.class, "TestEntity"); + entityRead.select(TestEntity.FOO, TestEntity.BAR, TestEntity.FOO); + + final ODataRequestRead requestRead = entityRead.toRequest(); + + assertThat(requestRead.getQueryString()).isEqualTo("$select=Foo,Bar"); + + assertThat(requestRead.getRelativeUri()).hasPath("/service/path/TestEntity").hasQuery("$select=Foo,Bar"); + } + + @Test + void testSelectAll() + { + final FluentHelperRead entityRead = + FluentHelperFactory.withServicePath("service/path/").read(TestEntity.class, "TestEntity"); + entityRead.select(TestEntity.ALL_FIELDS); + + final ODataRequestRead requestRead = entityRead.toRequest(); + + assertThat(requestRead.getQueryString()).isEqualTo("$select=*"); + + assertThat(requestRead.getRelativeUri()).hasPath("/service/path/TestEntity").hasQuery("$select=*"); + } + + @Test + void testExpand() + { + final FluentHelperRead entityRead = + FluentHelperFactory.withServicePath("service/path/").read(TestEntity.class, "TestEntity"); + entityRead.select(TestEntity.TO_PARENT, TestEntity.TO_CHILDREN.select(TestEntity.TO_PARENT)); + + final ODataRequestRead requestRead = entityRead.toRequest(); + + assertThat(requestRead.getQueryString()) + .isEqualTo("$select=to_Parent/*,to_Children/to_Parent/*&$expand=to_Parent,to_Children/to_Parent"); + + assertThat(requestRead.getRelativeUri()) + .hasPath("/service/path/TestEntity") + .hasQuery("$select=to_Parent/*,to_Children/to_Parent/*&$expand=to_Parent,to_Children/to_Parent"); + } + + // full set of generated VDM classes for TestEntity + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + public final static TestEntitySelectable ALL_FIELDS = new TestEntitySelectable() + { + @Getter + public String fieldName = "*"; + @Getter + public List selections = Collections.singletonList("*"); + }; + + public static final TestEntitySelectableOneToOneLink TO_PARENT = + new TestEntitySelectableOneToOneLink<>("to_Parent"); + public static final TestEntitySelectableLink TO_CHILDREN = + new TestEntitySelectableLink<>("to_Children"); + public static final TestEntitySelectableField FOO = new TestEntitySelectableField<>("Foo"); + public static final TestEntitySelectableField BAR = new TestEntitySelectableField<>("Bar"); + + @Getter + final String entityCollection = "TestEntity"; + + @Getter + private final String defaultServicePath = "/"; + + @Getter + private final Class type = TestEntity.class; + + @SerializedName( "to_Parent" ) + @JsonProperty( "to_Parent" ) + @ODataField( odataName = "to_Parent" ) + @Nullable + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private TestEntity toParent; + + @SerializedName( "to_Children" ) + @JsonProperty( "to_Children" ) + @ODataField( odataName = "to_Children" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toChildren; + + @SerializedName( "Foo" ) + @JsonProperty( "Foo" ) + @Nullable + @ODataField( odataName = "Foo" ) + private String foo; + + @SerializedName( "Bar" ) + @JsonProperty( "Bar" ) + @Nullable + @ODataField( odataName = "Bar" ) + private String bar; + } + + private interface TestEntitySelectable extends EntitySelectable + { + } + + private static class TestEntitySelectableField extends EntityField + implements + TestEntitySelectable + { + private TestEntitySelectableField( @Nonnull final String fieldName ) + { + super(fieldName); + } + } + + private static class TestEntitySelectableLink> + extends + EntityLink, TestEntity, ObjectT> + implements + TestEntitySelectable + { + private TestEntitySelectableLink( final String fieldName ) + { + super(fieldName); + } + + private TestEntitySelectableLink( + final EntityLink, TestEntity, ObjectT> toClone ) + { + super(toClone); + } + + @Nonnull + @Override + protected TestEntitySelectableLink translateLinkType( + final EntityLink, TestEntity, ObjectT> link ) + { + return new TestEntitySelectableLink(link); + } + } + + private static class TestEntitySelectableOneToOneLink> + extends + TestEntitySelectableLink + implements + OneToOneLink + { + private TestEntitySelectableOneToOneLink( final String fieldName ) + { + super(fieldName); + } + + @Nonnull + @Override + public ExpressionFluentHelper filter( + @Nonnull final ExpressionFluentHelper filterExpression ) + { + return super.filterOnOneToOneLink(filterExpression); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.java new file mode 100644 index 0000000000..57c13e6506 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.java @@ -0,0 +1,342 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.data.MapEntry.entry; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataDeserializationException; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.annotation.Key; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +class ODataSerializationTest +{ + // take note that we are NOT using the following values: + // "LocalDateTimeValue": "datetime'1992-01-01T00:00:00'" + // "OffsetDateTimeValue": "datetimeoffset'1992-01-01T00:00:00Z-04:00'" + // "LocalTimeValue": "time'PT13H20M'" + // "GuidValue": "guid'123e4567-e89b-12d3-a456-426614174000'" + private static final String SAMPLE_PAYLOAD = """ + { + "d" : { + "__metadata": { + "uri": "https://services.odata.org/OData/OData.svc/Categories(42)", + "etag": "W/\\"some-version-id\\"", + "type": "DataServiceProviderDemo.Category" + }, + "SByteValue": -127, + "Int16Value": 1337, + "IntegerValue": 42, + "Int64Value": 123456789000, + "DecimalValue": "123456.789", + "DoubleValue": 42.1, + "BooleanValue": true, + "StringValue": "Food", + "LocalDateTimeValue": "/Date(694224000000)/", + "OffsetDateTimeValue": "/Date(694224000000-0240)/", + "LocalTimeValue": "PT13H20M", + "GuidValue": "123e4567-e89b-12d3-a456-426614174000", + "to_Parent": { + "__deferred": { + "uri": "https://services.odata.org/OData/OData.svc/Categories(42)/to_Parent" + } + }, + "to_Children": { + "__deferred": { + "uri": "https://services.odata.org/OData/OData.svc/Categories(42)/to_Children" + } + }, + "UnmappedStringValue": "foo", + "UnmappedArrayValue": ["fizz","buzz"], + "UnmappedComplexValue": {"bar":"fizzbuzz"} + } + } + """; + + @Test + void testBrokenResponse() + { + // setup http response + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(new Header[0]).when(response).getHeaders(); + doReturn(201).when(response).getCode(); + doReturn(new StringEntity("{\"d\":{\"broken\"}}", ContentType.APPLICATION_JSON)).when(response).getEntity(); + + // setup generic request for reference + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + doReturn(ODataProtocol.V2).when(request).getProtocol(); + + // test entity deserialization + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + assertThatCode(() -> result.as(TestEntity.class)).isInstanceOf(ODataDeserializationException.class); + } + + @Test + void testNoValuesResponse() + { + // setup http response + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(new Header[0]).when(response).getHeaders(); + doReturn(201).when(response).getCode(); + doReturn(new StringEntity("{\"d\":{}}", ContentType.APPLICATION_JSON)).when(response).getEntity(); + + // setup generic request for reference + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + doReturn(ODataProtocol.V2).when(request).getProtocol(); + + // test entity deserialization + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final TestEntity entity = result.as(TestEntity.class); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + assertThat(entity.getIntegerValue()).isNull(); + assertThat(entity.getDecimalValue()).isNull(); + assertThat(entity.getDoubleValue()).isNull(); + assertThat(entity.getBooleanValue()).isNull(); + assertThat(entity.getLocalTimeValue()).isNull(); + assertThat(entity.getStringValue()).isNull(); + assertThat(entity.getGuidValue()).isNull(); + assertThat(entity.getLocalDateTimeValue()).isNull(); + assertThat(entity.getOffsetDateTimeValue()).isNull(); + assertThat(entity.getCustomFields()).isEmpty(); + } + + @Test + void testCreateResponse() + { + // setup http response + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(new Header[0]).when(response).getHeaders(); + doReturn(201).when(response).getCode(); + doReturn(new StringEntity(SAMPLE_PAYLOAD, ContentType.APPLICATION_JSON)).when(response).getEntity(); + + // setup generic request for reference + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + doReturn(ODataProtocol.V2).when(request).getProtocol(); + + // test entity deserialization + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final TestEntity entity = result.as(TestEntity.class); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly("W/\"some-version-id\""); + assertThat(entity.getSByteValue()).isEqualTo((byte) -127); + assertThat(entity.getInt16Value()).isEqualTo((short) 1337); + assertThat(entity.getIntegerValue()).isEqualTo(42); + assertThat(entity.getInt64Value()).isEqualTo(123456789000L); + assertThat(entity.getDecimalValue()).isEqualTo("123456.789"); + assertThat(entity.getDoubleValue()).isEqualTo(42.1); + assertThat(entity.getBooleanValue()).isTrue(); + assertThat(entity.getLocalTimeValue()).isEqualTo(LocalTime.of(13, 20, 0)); + assertThat(entity.getStringValue()).isEqualTo("Food"); + assertThat(entity.getGuidValue()).isEqualTo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); + assertThat(entity.getLocalDateTimeValue()).isEqualTo(LocalDate.of(1992, Month.JANUARY, 1).atStartOfDay()); + assertThat(entity.getOffsetDateTimeValue()) + .isEqualTo( + LocalDate + .of(1992, Month.JANUARY, 1) + .atStartOfDay() + .atZone(ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)))); + + assertThat(entity.getCustomFields()) + .containsExactly( + entry("UnmappedStringValue", "foo"), + entry("UnmappedArrayValue", Arrays.asList("fizz", "buzz")), + entry("UnmappedComplexValue", ImmutableMap.of("bar", "fizzbuzz"))); + } + + @Test + void testSerialisationForDateTimeAttributes() + { + final String SERIALIZED_ENTITY = """ + {\ + "IntegerValue":1,\ + "OffsetDateTimeValue":"/Date(694224000000-0240)/",\ + "LocalTimeValue":"PT13H20M0S",\ + "LocalDateTimeValue":"/Date(694224000000)/"\ + }\ + """; + final TestEntity entity = new TestEntity(); + + entity.setIntegerValue(1); + entity.setLocalDateTimeValue(LocalDate.of(1992, Month.JANUARY, 1).atStartOfDay()); + entity.setLocalTimeValue(LocalTime.of(13, 20, 0)); + entity + .setOffsetDateTimeValue( + LocalDate + .of(1992, Month.JANUARY, 1) + .atStartOfDay() + .atZone(ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)))); + + final String serialisedEntity = new Gson().toJson(entity); + + assertThat(serialisedEntity).isEqualTo(SERIALIZED_ENTITY); + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + @Getter + final String entityCollection = null; + + @Getter + private final String defaultServicePath = "/"; + + @Getter + private final Class type = TestEntity.class; + + @Key + @SerializedName( "IntegerValue" ) + @JsonProperty( "IntegerValue" ) + @Nullable + @ODataField( odataName = "IntegerValue" ) + private Integer integerValue; + + @SerializedName( "GuidValue" ) + @JsonProperty( "GuidValue" ) + @Nullable + @ODataField( odataName = "GuidValue" ) + private UUID guidValue; + + @SerializedName( "StringValue" ) + @JsonProperty( "StringValue" ) + @Nullable + @ODataField( odataName = "StringValue" ) + private String stringValue; + + @SerializedName( "OffsetDateTimeValue" ) + @JsonProperty( "OffsetDateTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonZonedDateTimeSerializer.class ) + @JsonDeserialize( + using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonZonedDateTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeAdapter.class ) + @ODataField( + odataName = "OffsetDateTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeCalendarConverter.class ) + private ZonedDateTime offsetDateTimeValue; + + @SerializedName( "to_Parent" ) + @JsonProperty( "to_Parent" ) + @ODataField( odataName = "to_Parent" ) + @Nullable + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private TestEntity toParent; + + @SerializedName( "to_Children" ) + @JsonProperty( "to_Children" ) + @ODataField( odataName = "to_Children" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toChildren; + + @SerializedName( "DecimalValue" ) + @JsonProperty( "DecimalValue" ) + @Nullable + @ODataField( odataName = "DecimalValue" ) + private BigDecimal decimalValue; + + @SerializedName( "DoubleValue" ) + @JsonProperty( "DoubleValue" ) + @Nullable + @ODataField( odataName = "DoubleValue" ) + private Double doubleValue; + + @SerializedName( "LocalTimeValue" ) + @JsonProperty( "LocalTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalTimeSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalTimeAdapter.class ) + @ODataField( + odataName = "LocalTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalTimeCalendarConverter.class ) + private LocalTime localTimeValue; + + @SerializedName( "LocalDateTimeValue" ) + @JsonProperty( "LocalDateTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalDateTimeSerializer.class ) + @JsonDeserialize( + using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalDateTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeAdapter.class ) + @ODataField( + odataName = "LocalDateTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeCalendarConverter.class ) + private LocalDateTime localDateTimeValue; + + @SerializedName( "BooleanValue" ) + @JsonProperty( "BooleanValue" ) + @Nullable + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataBooleanAdapter.class ) + @ODataField( odataName = "BooleanValue" ) + private Boolean booleanValue; + + @SerializedName( "SByteValue" ) + @JsonProperty( "SByteValue" ) + @Nullable + @ODataField( odataName = "SByteValue" ) + private Byte sByteValue; + + @SerializedName( "Int16Value" ) + @JsonProperty( "Int16Value" ) + @Nullable + @ODataField( odataName = "Int16Value" ) + private Short int16Value; + + @SerializedName( "Int64Value" ) + @JsonProperty( "Int64Value" ) + @Nullable + @ODataField( odataName = "Int64Value" ) + private Long int64Value; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.java new file mode 100644 index 0000000000..8c9960d45e --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.java @@ -0,0 +1,96 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchChangeSetFluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchFluentHelperBasic; + +import lombok.Getter; + +@WireMockTest +class ODataV2FunctionImportBatchIntegrationTest +{ + private static final String ODATA_ENDPOINT_URL = "/path/to/service"; + private static final String ODATA_ENDPOINT_BATCH_URL = ODATA_ENDPOINT_URL + "/$batch"; + + @Getter + public static class TestBatch extends BatchFluentHelperBasic + { + private final String servicePathForBatchRequest = ODATA_ENDPOINT_URL; + + @Nonnull + @Override + protected TestBatch getThis() + { + return this; + } + + @Nonnull + @Override + public TestBatchChangeset beginChangeSet() + { + return new TestBatchChangeset(this); + } + } + + public static class TestBatchChangeset extends BatchChangeSetFluentHelperBasic + { + public TestBatchChangeset( final TestBatch parent ) + { + super(parent, parent); + } + + @Nonnull + @Override + protected TestBatchChangeset getThis() + { + return this; + } + } + + @Test + void testFunctionImportWithPostInChangeSet( @Nonnull final WireMockRuntimeInfo wm ) + { + stubFor(head(urlEqualTo(ODATA_ENDPOINT_URL)).willReturn(noContent())); + stubFor(post(urlEqualTo(ODATA_ENDPOINT_BATCH_URL)).willReturn(ok())); + + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final Map parameters = new LinkedHashMap<>(); + parameters.put("FileDocumentYear", "2015"); + parameters.put("FileDocument", "00281"); + parameters.put("FileDocumentItem", "1"); + parameters.put("PostingDate", LocalDateTime.of(2015, 1, 12, 12, 12)); + + final FluentHelperFunction functionImport = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .functionSinglePost(parameters, "CancelItem", Void.class); + + new TestBatch().beginChangeSet().addFunctionImport(functionImport).endChangeSet().executeRequest(destination); + + final String functionImportPost = + "POST CancelItem?FileDocumentYear='2015'&FileDocument='00281'&FileDocumentItem='1'&PostingDate=datetime'2015-01-12T12:12:00' HTTP/1.1"; + + verify(postRequestedFor(urlEqualTo(ODATA_ENDPOINT_BATCH_URL)).withRequestBody(containing(functionImportPost))); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.java new file mode 100644 index 0000000000..cda983383e --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.java @@ -0,0 +1,83 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import java.util.Collections; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchFluentHelperBasic; +import com.sap.cloud.sdk.datamodel.odata.helper.batch.FluentHelperBatchEndChangeSet; + +@WireMockTest +class ODataV2FunctionImportGetBatchIntegrationTest +{ + private static final String ODATA_ENDPOINT_URL = "/path/to/service"; + private static final String ODATA_ENDPOINT_BATCH_URL = ODATA_ENDPOINT_URL + "/$batch"; + + public static class TestBatch extends BatchFluentHelperBasic + implements + FluentHelperBatchEndChangeSet + { + @Nonnull + @Override + protected String getServicePathForBatchRequest() + { + return ODATA_ENDPOINT_URL; + } + + @Nonnull + @Override + protected TestBatch getThis() + { + return this; + } + + @Nonnull + @Override + public TestBatch beginChangeSet() + { + return this; + } + + @Nonnull + @Override + public TestBatch endChangeSet() + { + return this; + } + } + + @Test + void testFunctionImportWithGetInReadOperation( @Nonnull final WireMockRuntimeInfo wm ) + { + stubFor(head(urlEqualTo(ODATA_ENDPOINT_URL)).willReturn(noContent())); + stubFor(post(urlEqualTo(ODATA_ENDPOINT_BATCH_URL)).willReturn(ok())); + + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final FluentHelperFunction functionImport = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .functionSingleGet(Collections.emptyMap(), "Get_TestQueue", String.class); + + new TestBatch().addReadOperations(functionImport).executeRequest(destination); + + final String functionImportGet = "GET Get_TestQueue HTTP/1.1"; + + verify(postRequestedFor(urlEqualTo(ODATA_ENDPOINT_BATCH_URL)).withRequestBody(containing(functionImportGet))); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.java new file mode 100644 index 0000000000..ec846cf235 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.java @@ -0,0 +1,232 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToIgnoreCase; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.time.LocalDateTime; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.core5.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@WireMockTest +class ODataV2FunctionImportIntegrationTest +{ + private static final String ODATA_ENDPOINT_URL = "/path/to/service"; + private static final String ODATA_FUNCTION_IMPORT_URL = ODATA_ENDPOINT_URL + "/CancelItem.*"; + private static final String CSRF_TOKEN = "awesome-token"; + private static final String CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + + private static final String XML_ERROR_STRING = "an exception was raised ";; + private static final String JSON_ERROR_STRING = "{\"error\": \"an exception was raised\"}"; + private static final String RESPONSE = """ + {"d": { + "CancelItem": { + "__metadata": { "type": "API_TEST_SRV/CancelItem" }, + "SomeField": "1010" + } + }} + """; + + @Data + @EqualsAndHashCode( callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = "TestEntities"; + private final Class type = TestEntity.class; + + @SerializedName( "SomeField" ) + @JsonProperty( "SomeField" ) + @ODataField( odataName = "SomeField" ) + private String someField; + } + + private DefaultHttpDestination destination; + + @BeforeEach + void before( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Test + void testFunctionImportWithCsrfToken() + { + wiremockIssueCsrfTokenWithStatusCodeOk(); + + wiremockSendSuccesfulJsonResponse(); + + final TestEntity materialDocumentItem = getCancelItemFluentHelper().executeRequest(destination); + + assertOnFunctionImportResponseEntity(materialDocumentItem); + } + + @Test + void testFunctionImportWithCsrfTokenAndServerErrorResponse() + { + wiremockIssueCsrfToken(WireMock.serverError()); + + wiremockSendSuccesfulJsonResponse(); + + final TestEntity materialDocumentItem = getCancelItemFluentHelper().executeRequest(destination); + + assertOnFunctionImportResponseEntity(materialDocumentItem); + } + + @Test + void testFunctionImportWithCsrfTokenAndMethodNotAllowedResponse() + { + wiremockIssueCsrfToken(WireMock.status(HttpStatus.SC_METHOD_NOT_ALLOWED)); + + wiremockSendSuccesfulJsonResponse(); + + final TestEntity materialDocumentItem = getCancelItemFluentHelper().executeRequest(destination); + + assertOnFunctionImportResponseEntity(materialDocumentItem); + } + + @Test + void testFunctionImportWithoutCsrfToken() + { + //Return OK but without csrf token in response header + stubFor( + head(urlEqualTo(ODATA_ENDPOINT_URL + "/")) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase("Fetch")) + .willReturn(WireMock.ok())); + + stubFor(post(urlMatching(ODATA_FUNCTION_IMPORT_URL)).willReturn(okJson(RESPONSE))); + + final TestEntity materialDocumentItem = getCancelItemFluentHelper().executeRequest(destination); + + assertOnFunctionImportResponseEntity(materialDocumentItem); + + verify(postRequestedFor(urlMatching(ODATA_FUNCTION_IMPORT_URL)).withoutHeader(CSRF_TOKEN_HEADER_KEY)); + } + + @Test + void testFunctionImportWithJsonError() + { + wiremockIssueCsrfTokenWithStatusCodeOk(); + + stubFor( + post(urlMatching(ODATA_FUNCTION_IMPORT_URL)).willReturn(WireMock.badRequest().withBody(JSON_ERROR_STRING))); + + assertThatExceptionOfType(ODataException.class) + .isThrownBy(() -> getCancelItemFluentHelper().executeRequest(destination)); + } + + @Test + void testFunctionImportReturnsSanitizedXmlErrorBody() + { + wiremockIssueCsrfTokenWithStatusCodeOk(); + + stubFor( + post(urlMatching(ODATA_FUNCTION_IMPORT_URL)).willReturn(WireMock.badRequest().withBody(XML_ERROR_STRING))); + + assertThatExceptionOfType(ODataException.class) + .isThrownBy(() -> getCancelItemFluentHelper().executeRequest(destination)); + } + + /* + * This test verifies that header manipulation attempts (sneaking in new line characters in "user-supplied" HTTP + * header) are prevented. At this place this should not be necessary, as the system we receive the CSRF token from + * is the same one receiving the next request with this token, so any manipulation would hit the system that sent us + * the manipulated csrf token in the first place. But, better be safe than sorry, so we remove all non-printable + * characters. + * Additional note: As tomcat (so also wiremock) automatically removes new line characters we cannot directly test + * this logic here, therefore we test it with a tab (\t) character. + */ + @Test + void testNonPrintableCharactersRemovedFromCsrfTokenValue() + { + final String tokenWithNonPrintableCharacters = CSRF_TOKEN + "\t%0a%0d"; + final String tokenWithoutNonPrintableCharacters = CSRF_TOKEN + "%0a%0d"; + + stubFor( + head(urlEqualTo(ODATA_ENDPOINT_URL + "/")) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase("Fetch")) + .willReturn(ok().withHeader(CSRF_TOKEN_HEADER_KEY, tokenWithNonPrintableCharacters))); + + stubFor( + post(urlMatching(ODATA_FUNCTION_IMPORT_URL)) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalTo(tokenWithoutNonPrintableCharacters)) + .willReturn(okJson(RESPONSE))); + + getCancelItemFluentHelper().executeRequest(destination); + + verify( + postRequestedFor(urlMatching(ODATA_FUNCTION_IMPORT_URL)) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalTo(tokenWithoutNonPrintableCharacters))); + } + + private void wiremockIssueCsrfToken( final ResponseDefinitionBuilder responseDefinitionBuilder ) + { + stubFor( + head(urlEqualTo(ODATA_ENDPOINT_URL + "/")) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase("Fetch")) + .willReturn(responseDefinitionBuilder.withHeader(CSRF_TOKEN_HEADER_KEY, CSRF_TOKEN))); + } + + private void wiremockIssueCsrfTokenWithStatusCodeOk() + { + wiremockIssueCsrfToken(WireMock.ok()); + } + + private void assertOnFunctionImportResponseEntity( @Nullable final TestEntity materialDocumentItem ) + { + assertThat(materialDocumentItem).isNotNull(); + assertThat(materialDocumentItem.getSomeField()).isEqualTo("1010"); + } + + private void wiremockSendSuccesfulJsonResponse() + { + stubFor( + post(urlMatching(ODATA_FUNCTION_IMPORT_URL)) + .withHeader(CSRF_TOKEN_HEADER_KEY, equalTo(CSRF_TOKEN)) + .willReturn(okJson(RESPONSE))); + } + + private static FluentHelperFunction getCancelItemFluentHelper() + { + final Map parameters = new LinkedHashMap<>(); + parameters.put("FileDocumentYear", "2015"); + parameters.put("FileDocument", "00281"); + parameters.put("FileDocumentItem", "1"); + parameters.put("PostingDate", LocalDateTime.of(2015, 1, 12, 12, 12)); + return FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .functionSinglePost(parameters, "CancelItem", TestEntity.class); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.java new file mode 100644 index 0000000000..f3f29fc6d8 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.java @@ -0,0 +1,134 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.sap.cloud.sdk.datamodel.odata.helper.PaginationUnitTest.Customer; +import static com.sap.cloud.sdk.datamodel.odata.helper.PaginationUnitTest.newCustomerRead; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.net.URI; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; + +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; + +@Disabled( "Test runs against a v4 reference service on odata.org. Use it only to manually verify behaviour." ) +class PaginationIntegrationTest +{ + private static final int PAGE_SIZE = 20; + private static final ArgumentMatcher URI_WITHOUT_SKIP_TOKEN = uri -> !uri.getQuery().contains("$skiptoken"); + private static final ArgumentMatcher URL_WITH_SKIP_TOKEN = uri -> uri.getQuery().contains("$skiptoken"); + + private final HttpDestination destination = DefaultHttpDestination.builder("https://services.odata.org").build(); + + @Test + void testGetAll() + { + final HttpDestination destination = spy(this.destination); + + final List result = + newCustomerRead().select(Customer.CUSTOMER_ID).withPreferredPageSize(PAGE_SIZE).executeRequest(destination); + + verify(destination, times(1)).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, atLeastOnce()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + + assertThat(result) + .isNotEmpty() + .extracting(PaginationUnitTest.Customer::getCustomerId) + .allMatch(Objects::nonNull); + } + + @Test + void testGetAllIteratingEntities() + { + final HttpDestination destination = spy(this.destination); + + final Iterable result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(20) + .iteratingEntities() + .executeRequest(destination); + + verify(destination, times(1)).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, never()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + reset(destination); + + int countEntities = 0; + for( final Customer entity : result ) { + countEntities++; + assertThat(entity).extracting(Customer::getCustomerId).isNotNull(); + } + + verify(destination, never()).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, atLeastOnce()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + assertThat(countEntities).isGreaterThan(0); + } + + @Test + void testGetAllStreamingEntities() + { + final HttpDestination destination = spy(this.destination); + + final Stream result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(20) + .streamingEntities() + .executeRequest(destination); + + verify(destination, times(1)).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, never()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + reset(destination); + + final Stream intermediateStream = result.map(Customer::getCustomerId).peek(Objects::requireNonNull); + verify(destination, never()).getHeaders(any()); + + final long countEntities = intermediateStream.count(); + assertThat(countEntities).isGreaterThan(0); + verify(destination, never()).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, atLeastOnce()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + } + + @Test + void testGetAllIteratingPages() + { + final HttpDestination destination = spy(this.destination); + + final Iterable> result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(PAGE_SIZE) + .iteratingPages() + .executeRequest(destination); + + verify(destination, times(1)).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, never()).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + reset(destination); + + int countPages = 0; + int countEntities = 0; + for( final List entities : result ) { + countPages++; + countEntities += entities.size(); + assertThat(entities).extracting(Customer::getCustomerId).allMatch(Objects::nonNull); + } + + verify(destination, never()).getHeaders(argThat(URI_WITHOUT_SKIP_TOKEN)); + verify(destination, times(countPages - 1)).getHeaders(argThat(URL_WITH_SKIP_TOKEN)); + assertThat(countEntities).isGreaterThan(0); + assertThat(countPages).isGreaterThan(1); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.java new file mode 100644 index 0000000000..181a72f2fa --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.java @@ -0,0 +1,232 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.absent; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.github.tomakehurst.wiremock.matching.UrlPattern; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.annotation.Key; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@WireMockTest +class PaginationUnitTest +{ + private static final int PAGE_SIZE = 20; + private static final int ENTITIES_COUNT = 91; + private static final int PAGES_COUNT = 5; + + private DefaultHttpDestination destination; + + // corresponds to https://services.odata.org/V4/Northwind/Northwind.svc/Customers?$select=CustomerID + final String page1 = + "{ \"d\" : { \"results\": [ { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ALFKI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ALFKI\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ANATR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ANATR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ANTON')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ANTON\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('AROUT')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"AROUT\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BERGS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BERGS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BLAUS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BLAUS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BLONP')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BLONP\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BOLID')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BOLID\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BONAP')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BONAP\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BOTTM')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BOTTM\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('BSBEV')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"BSBEV\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('CACTU')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"CACTU\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('CENTC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"CENTC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('CHOPS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"CHOPS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('COMMI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"COMMI\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('CONSH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"CONSH\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('DRACD')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"DRACD\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('DUMON')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"DUMON\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('EASTC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"EASTC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ERNSH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ERNSH\" } ], \"__next\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers?$select=CustomerID&$skiptoken='ERNSH'\" } }"; + final String page2 = + "{ \"d\" : { \"results\": [ { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FAMIA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FAMIA\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FISSA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FISSA\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FOLIG')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FOLIG\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FOLKO')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FOLKO\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FRANK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FRANK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FRANR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FRANR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FRANS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FRANS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('FURIB')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"FURIB\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('GALED')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"GALED\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('GODOS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"GODOS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('GOURL')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"GOURL\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('GREAL')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"GREAL\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('GROSR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"GROSR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('HANAR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"HANAR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('HILAA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"HILAA\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('HUNGC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"HUNGC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('HUNGO')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"HUNGO\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ISLAT')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ISLAT\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('KOENE')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"KOENE\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LACOR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LACOR\" } ], \"__next\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers?$select=CustomerID&$skiptoken='LACOR'\" } }"; + final String page3 = + "{ \"d\" : { \"results\": [ { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LAMAI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LAMAI\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LAUGB')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LAUGB\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LAZYK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LAZYK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LEHMS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LEHMS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LETSS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LETSS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LILAS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LILAS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LINOD')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LINOD\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('LONEP')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"LONEP\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('MAGAA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"MAGAA\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('MAISD')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"MAISD\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('MEREP')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"MEREP\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('MORGK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"MORGK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('NORTS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"NORTS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('OCEAN')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"OCEAN\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('OLDWO')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"OLDWO\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('OTTIK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"OTTIK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('PARIS')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"PARIS\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('PERIC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"PERIC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('PICCO')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"PICCO\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('PRINI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"PRINI\" } ], \"__next\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers?$select=CustomerID&$skiptoken='PRINI'\" } }"; + final String page4 = + "{ \"d\" : { \"results\": [ { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('QUEDE')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"QUEDE\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('QUEEN')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"QUEEN\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('QUICK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"QUICK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('RANCH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"RANCH\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('RATTC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"RATTC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('REGGC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"REGGC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('RICAR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"RICAR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('RICSU')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"RICSU\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('ROMEY')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"ROMEY\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SANTG')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SANTG\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SAVEA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SAVEA\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SEVES')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SEVES\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SIMOB')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SIMOB\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SPECD')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SPECD\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SPLIR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SPLIR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('SUPRD')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"SUPRD\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('THEBI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"THEBI\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('THECR')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"THECR\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('TOMSP')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"TOMSP\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('TORTU')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"TORTU\" } ], \"__next\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers?$select=CustomerID&$skiptoken='TORTU'\" } }"; + final String page5 = + "{ \"d\" : { \"results\": [ { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('TRADH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"TRADH\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('TRAIH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"TRAIH\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('VAFFE')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"VAFFE\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('VICTE')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"VICTE\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('VINET')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"VINET\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WANDK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WANDK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WARTH')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WARTH\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WELLI')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WELLI\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WHITC')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WHITC\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WILMK')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WILMK\" }, { \"__metadata\": { \"uri\": \"https://services.odata.org/V2/Northwind/Northwind.svc/Customers('WOLZA')\", \"type\": \"NorthwindModel.Customer\" }, \"CustomerID\": \"WOLZA\" } ] } }"; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + stubFor( + get(UrlPattern.ANY) + .withHeader("Prefer", equalTo("odata.maxpagesize=20")) + .withQueryParam("$skiptoken", absent()) + .willReturn(okJson(page1))); + stubFor( + get(UrlPattern.ANY) + .withHeader("Prefer", equalTo("odata.maxpagesize=20")) + .withQueryParam("$skiptoken", equalTo("'ERNSH'")) + .willReturn(okJson(page2))); + stubFor( + get(UrlPattern.ANY) + .withHeader("Prefer", equalTo("odata.maxpagesize=20")) + .withQueryParam("$skiptoken", equalTo("'LACOR'")) + .willReturn(okJson(page3))); + stubFor( + get(UrlPattern.ANY) + .withHeader("Prefer", equalTo("odata.maxpagesize=20")) + .withQueryParam("$skiptoken", equalTo("'PRINI'")) + .willReturn(okJson(page4))); + stubFor( + get(UrlPattern.ANY) + .withHeader("Prefer", equalTo("odata.maxpagesize=20")) + .withQueryParam("$skiptoken", equalTo("'TORTU'")) + .willReturn(okJson(page5))); + + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Test + void testGetAll() + { + final List result = + newCustomerRead().select(Customer.CUSTOMER_ID).withPreferredPageSize(PAGE_SIZE).executeRequest(destination); + + verify(PAGES_COUNT, getRequestedFor(UrlPattern.ANY)); + + Assertions + .assertThat(result) + .hasSize(ENTITIES_COUNT) + .extracting(Customer::getCustomerId) + .allMatch(Objects::nonNull); + } + + @Test + void testGetAllIteratingEntities() + { + final Iterable result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(PAGE_SIZE) + .iteratingEntities() + .executeRequest(destination); + + verify(1, getRequestedFor(UrlPattern.ANY)); + + int countPages = 0; + int countEntities = 0; + for( final Customer entity : result ) { + if( countEntities++ % PAGE_SIZE == 0 ) { + verify(++countPages, getRequestedFor(UrlPattern.ANY)); + } + assertThat(entity).extracting(Customer::getCustomerId).isNotNull(); + } + + assertThat(countEntities).isEqualTo(ENTITIES_COUNT); + verify(PAGES_COUNT, getRequestedFor(UrlPattern.ANY)); + } + + @Test + void testGetAllStreamingEntities() + { + final Stream result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(PAGE_SIZE) + .streamingEntities() + .executeRequest(destination); + + verify(1, getRequestedFor(UrlPattern.ANY)); + + final Stream intermediateStream = result.map(Customer::getCustomerId).peek(Objects::requireNonNull); + verify(1, getRequestedFor(UrlPattern.ANY)); + + final long countEntities = intermediateStream.count(); + assertThat(countEntities).isEqualTo(ENTITIES_COUNT); + verify(PAGES_COUNT, getRequestedFor(UrlPattern.ANY)); + + // repeated access to stream is not permitted as of Java API + assertThatIllegalStateException() + .isThrownBy(result::count) + .withMessage("stream has already been operated upon or closed"); + } + + @Test + void testGetAllIteratingPages() + { + final Iterable> result = + newCustomerRead() + .select(Customer.CUSTOMER_ID) + .withPreferredPageSize(PAGE_SIZE) + .iteratingPages() + .executeRequest(destination); + + verify(1, getRequestedFor(UrlPattern.ANY)); + + int countPages = 0; + int countEntities = 0; + for( final List entities : result ) { + verify(++countPages, getRequestedFor(UrlPattern.ANY)); + countEntities += entities.size(); + Assertions.assertThat(entities).extracting(Customer::getCustomerId).allMatch(Objects::nonNull); + } + assertThat(countEntities).isEqualTo(ENTITIES_COUNT); + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class Customer extends VdmEntity + { + private static final String SERVICE_PATH = "/V2/Northwind/Northwind.svc"; + + @Getter + final String entityCollection = "Customers"; + + @Getter + private final String defaultServicePath = SERVICE_PATH; + + @Getter + private final Class type = + com.sap.cloud.sdk.datamodel.odata.helper.TestVdmEntity.class; + + @Key + @SerializedName( "CustomerID" ) + @JsonProperty( "CustomerID" ) + @Nullable + @ODataField( odataName = "CustomerID" ) + private String customerId; + + public final static CustomerField CUSTOMER_ID = new CustomerField("CustomerID"); + } + + public static class CustomerField extends EntityField implements CustomerSelectable + { + public CustomerField( final String fieldName ) + { + super(fieldName); + } + } + + public interface CustomerSelectable extends EntitySelectable + { + } + + static < + T extends FluentHelperRead> + FluentHelperRead + newCustomerRead() + { + return FluentHelperFactory.withServicePath(Customer.SERVICE_PATH).read(Customer.class, "Customers"); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.java new file mode 100644 index 0000000000..c3cdcac038 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.java @@ -0,0 +1,46 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.Test; + +import lombok.SneakyThrows; + +class SerializerRaceConditionTest +{ + @SneakyThrows + @Test + void testRaceCondition() + { + final TestVdmEntity entity = new TestVdmEntity(); + entity.setStringValue("string"); + entity.setDoubleValue(13.37); + entity.setBooleanValue(true); + entity.setIntegerValue(42); + entity.setDecimalValue(BigDecimal.ONE); + entity.setLocalDateTimeValue(LocalDateTime.of(2000, 1, 1, 0, 0)); + entity.setOffsetDateTimeValue(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)); + + final String EXPECTED_JSON = + "{\"IntegerValue\":42,\"StringValue\":\"string\",\"OffsetDateTimeValue\":\"/Date(946684800000)/\",\"DecimalValue\":\"1\",\"DoubleValue\":\"13.37\",\"LocalDateTimeValue\":\"/Date(946684800000)/\",\"BooleanValue\":true}"; + + final Collection> tasks = + Collections.nCopies(10_000, () -> ODataEntitySerializer.serializeEntityForCreate(entity)); + + final ExecutorService executor = Executors.newCachedThreadPool(); + for( final Future future : executor.invokeAll(tasks) ) { + assertThat(future.get()).isEqualTo(EXPECTED_JSON); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.java new file mode 100644 index 0000000000..f53cb2406d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.java @@ -0,0 +1,433 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static java.lang.String.format; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/* + * SERVICE A: + * GET /FunctionToMedia(Param1,Param2) -> multiple entities from A_MediaType + * GET /A_MediaEntitySet(Id)/$value -> media lookup + * + * SERVICE B: + * GET /A_EntitySet -> multiple entities from A_EntityType + * GET /A_EntitySet(Id) -> single entity from A_EntityType + * GET /A_EntitySet(Id)/to_RelatedEntity -> single entity from A_RelatedType + * GET /A_EntitySet(Id)/to_RelatedEntity/to_TransitiveEntity -> multiple entities from A_TransitiveType + * GET /A_RelatedEntitySet(Id) -> single entity from A_RelatedType + * GET /A_RelatedEntitySet(Id)/to_TransitiveEntity -> multiple entities from A_TransitiveType + */ +@WireMockTest +class ServicePathTest +{ + private static final String SERVICE_A = "/path/to/serviceA"; + private static final String SERVICE_B = "/path/to/serviceB"; + + private HttpDestination destination; + + @BeforeEach + void setupDestination( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + stubFor(head(urlEqualTo(SERVICE_A)).willReturn(ok())); + stubFor(head(urlEqualTo(SERVICE_B)).willReturn(ok())); + } + + @Test + void testChildEntityFromGet() + { + final String responseEntityListWithRelated = + createPayloadMultiple(createTestEntity("72", createRelatedEntity("13857", null))); + stubFor(get(urlPathMatching(SERVICE_B + "/A_EntitySet")).willReturn(okJson(responseEntityListWithRelated))); + + final String responseEntityListTransitive = createPayloadMultiple(createTransitiveEntity("Bar")); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity")) + .willReturn(okJson(responseEntityListTransitive))); + + final List testEntitys = + FluentHelperFactory + .withServicePath(SERVICE_B) + .read(TestEntity.class, "A_EntitySet") + .select(TestEntity.TO_RELATED_ENTITY) + .executeRequest(destination); + + final RelatedEntity bpRelatedEntity = testEntitys.get(0).getRelatedEntity(); + assertThat(bpRelatedEntity).isNotNull(); + + final List bpTransitiveEntity = bpRelatedEntity.fetchTransitiveEntity(); + assertThat(bpTransitiveEntity).isNotNull(); + + verify(getRequestedFor(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity"))); + } + + @Test + void testChildEntityFromGetByKey() + { + final String resonseEntityWithRelated = + createPayloadSingle(createTestEntity("72", createRelatedEntity("13857", null))); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_EntitySet\\('(.*)'\\)")).willReturn(okJson(resonseEntityWithRelated))); + + final String responseEntityListTransitive = createPayloadMultiple(createTransitiveEntity("Bar")); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity")) + .willReturn(okJson(responseEntityListTransitive))); + + final TestEntity testEntity = + FluentHelperFactory + .withServicePath(SERVICE_B) + .readByKey(TestEntity.class, "A_EntitySet", Collections.singletonMap("EntityId", "72")) + .select(TestEntity.TO_RELATED_ENTITY) + .executeRequest(destination); + + final RelatedEntity bpRelatedEntity = testEntity.getRelatedEntity(); + assertThat(bpRelatedEntity).isNotNull(); + + final List bpTransitiveEntity = bpRelatedEntity.fetchTransitiveEntity(); + assertThat(bpTransitiveEntity).isNotNull(); + + verify(getRequestedFor(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity"))); + } + + @Test + void testChildEntityFromCreate() + { + final String resonseEntityWithRelated = + createPayloadSingle(createTestEntity("72", createRelatedEntity("13857", null))); + stubFor(post(urlPathMatching(SERVICE_B + "/A_EntitySet")).willReturn(okJson(resonseEntityWithRelated))); + + final String responseEntityListTransitive = createPayloadMultiple(createTransitiveEntity("Bar")); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity")) + .willReturn(okJson(responseEntityListTransitive))); + + TestEntity testEntity = new TestEntity(); + + testEntity = + FluentHelperFactory + .withServicePath(SERVICE_B) + .create("A_EntitySet", testEntity) + .executeRequest(destination) + .getModifiedEntity(); + + final RelatedEntity bpRelatedEntity = testEntity.getRelatedEntity(); + assertThat(bpRelatedEntity).isNotNull(); + + final List bpTransitiveEntity = bpRelatedEntity.fetchTransitiveEntity(); + assertThat(bpTransitiveEntity).isNotNull(); + + verify(getRequestedFor(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity"))); + } + + @Test + void testChildEntityFromUpdate() + { + final String responseEntity = createPayloadSingle(createTestEntity("72", null)); + stubFor(get(urlPathMatching(SERVICE_B + "/A_EntitySet\\('(.*)'\\)")).willReturn(okJson(responseEntity))); + + final String responseEntityWithRelated = + createPayloadSingle(createTestEntity("72", createRelatedEntity("13857", null))); + stubFor( + patch(urlPathMatching(SERVICE_B + "/A_EntitySet\\('(.*)'\\)")) + .willReturn(okJson(responseEntityWithRelated))); + + final String responseEntityListTransitive = createPayloadMultiple(createTransitiveEntity("Bar")); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity")) + .willReturn(okJson(responseEntityListTransitive))); + + TestEntity testEntity = + FluentHelperFactory + .withServicePath(SERVICE_B) + .readByKey(TestEntity.class, "A_EntitySet", Collections.singletonMap("EntityId", "72")) + .executeRequest(destination); + + testEntity.setRelatedEntity(new RelatedEntity()); + + testEntity = + FluentHelperFactory + .withServicePath(SERVICE_B) + .update("A_EntitySet", testEntity) + .modifyingEntity() + .executeRequest(destination) + .getModifiedEntity(); + + final RelatedEntity bpRelatedEntity = testEntity.getRelatedEntity(); + assertThat(bpRelatedEntity).isNotNull(); + + final List bpTransitiveEntity = bpRelatedEntity.fetchTransitiveEntity(); + assertThat(bpTransitiveEntity).isNotNull(); + + verify(getRequestedFor(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity"))); + } + + @Test + void testChildEntityFromNavigationProperty() + { + final String responseEntity = createPayloadSingle(createTestEntity("72", null)); + stubFor(get(urlPathMatching(SERVICE_B + "/A_EntitySet\\('(.*)'\\)")).willReturn(okJson(responseEntity))); + + final String responseEntityWithRelated = createPayloadSingle(createRelatedEntity("13857", null)); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_EntitySet\\('(.*)'\\)/to_RelatedEntity")) + .willReturn(okJson(responseEntityWithRelated))); + + final String responseEntityListTransitive = createPayloadMultiple(createTransitiveEntity("Bar")); + stubFor( + get(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity")) + .willReturn(okJson(responseEntityListTransitive))); + + final TestEntity testEntity = + FluentHelperFactory + .withServicePath(SERVICE_B) + .readByKey(TestEntity.class, "A_EntitySet", Collections.singletonMap("EntityId", "72")) + .executeRequest(destination); + + final RelatedEntity bpRelatedEntity = testEntity.fetchRelatedEntity(); + assertThat(bpRelatedEntity).isNotNull(); + + final List bpTransitiveEntity = bpRelatedEntity.fetchTransitiveEntity(); + assertThat(bpTransitiveEntity).isNotNull(); + + verify(getRequestedFor(urlPathMatching(SERVICE_B + "/A_RelatedEntitySet\\('(.*)'\\)/to_TransitiveEntity"))); + } + + @Test + void testChildEntityFromFunctionImport() + throws IOException + { + final String responseFunctionImport = + createPayloadMultiple(createTransitiveEntity("100"), createTransitiveEntity("101")); + stubFor( + get(urlPathMatching(SERVICE_A + "/FunctionToMedia")) + .withQueryParam("Param1", equalTo("'MARA'")) + .withQueryParam("Param2", equalTo("'SAPTEST'")) + .willReturn(okJson(responseFunctionImport))); + + stubFor( + get(urlPathMatching(SERVICE_A + "/A_MediaEntitySet\\((.*)\\)/\\$value")) + .willReturn(okForContentType("text/plain", "Test file content"))); + + final Map parameters = new LinkedHashMap<>(); + parameters.put("Param1", "MARA"); + parameters.put("Param2", "SAPTEST"); + + final List attachments = + FluentHelperFactory + .withServicePath(SERVICE_A) + .functionMultipleGet(parameters, "FunctionToMedia", MediaEntity.class) + .executeRequest(destination); + + for( final MediaEntity attachmentContent : attachments ) { + try( final InputStream attachmentMedia = attachmentContent.fetchMediaStream() ) { + assertThat(attachmentMedia).isNotNull(); + assertThat(attachmentMedia.available()).isGreaterThan(0); + } + } + } + + private static String createPayloadSingle( final String payload ) + { + return format("{\"d\":%s}", payload); + } + + private static String createPayloadMultiple( final String... payloads ) + { + return createPayloadSingle(format("{\"results\":[%s]}", String.join(",", payloads))); + } + + private static String createTestEntity( final String id, final String rel ) + { + final String uri = format("https://127.0.0.1/path/to/serviceB/A_EntitySet('%s')", id); + final String m = format("{\"id\":\"%s\",\"uri\":\"%s\",\"type\":\"ServiceB.A_EntityType\"}", uri, uri); + final String r = rel != null ? rel : format("{\"__deferred\":{\"uri\":\"%s/to_RelatedEntity\"}}", uri); + return format("{\"__metadata\":%s,\"EntityId\":\"%s\",\"to_RelatedEntity\":%s}", m, id, r); + } + + private static String createRelatedEntity( final String id, final String trn ) + { + final String uri = format("https://127.0.0.1/path/to/serviceB/A_RelatedEntitySet('%s')", id); + final String m = format("{\"id\":\"%s\",\"uri\":\"%s\",\"type\":\"ServiceB.A_RelatedType\"}", uri, uri); + final String t = trn != null ? trn : format("{\"__deferred\":{\"uri\":\"%s/to_TransitiveEntity\"}}", uri); + return format("{\"__metadata\":%s,\"RelatedId\":\"%s\",\"to_TransitiveEntity\":%s}", m, id, t); + } + + private static String createTransitiveEntity( final String id ) + { + final String uri = format("https://127.0.0.1/path/to/serviceB/A_TransitiveSet('%s')", id); + final String format = + "{\"id\":\"%s\",\"uri\":\"%s\",\"type\":\"ServiceB.A_MediaType\",\"content_type\":\"text/plain\",\"media_src\":\"%s/$value\"}"; + return format("{\"__metadata\":%s,\"MediaId\":\"%s\"}", format(format, uri, uri, uri), id); + } + + @Data + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = "A_EntitySet"; + private final Class type = TestEntity.class; + + @SerializedName( "EntityId" ) + @JsonProperty( "EntityId" ) + @ODataField( odataName = "EntityId" ) + private String entityId; + + @SerializedName( "to_RelatedEntity" ) + @JsonProperty( "to_RelatedEntity" ) + @ODataField( odataName = "to_RelatedEntity" ) + private RelatedEntity relatedEntity; + + public static final TestEntityLink TO_RELATED_ENTITY = new TestEntityLink<>("to_RelatedEntity"); + + @Nonnull + @Override + protected Map getKey() + { + return ImmutableMap.of("EntityId", getEntityId()); + } + + @Nonnull + @Override + protected Map toMapOfNavigationProperties() + { + return Collections.singletonMap("to_RelatedEntity", relatedEntity); + } + + public RelatedEntity fetchRelatedEntity() + { + return fetchFieldAsSingle("to_RelatedEntity", RelatedEntity.class); + } + + public void setRelatedEntity( final RelatedEntity relatedEntity ) + { + rememberChangedField("to_RelatedEntity", this.relatedEntity); + this.relatedEntity = relatedEntity; + } + } + + @Data + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class RelatedEntity extends VdmEntity + { + private final String entityCollection = "A_RelatedEntitySet"; + private final Class type = RelatedEntity.class; + + @SerializedName( "RelatedId" ) + @JsonProperty( "RelatedId" ) + @ODataField( odataName = "RelatedId" ) + private String relatedId; + + @SerializedName( "to_TransitiveEntity" ) + @JsonProperty( "to_TransitiveEntity" ) + @ODataField( odataName = "to_TransitiveEntity" ) + private List transitiveEntity; + + public static final RelatedEntityLink TO_RELATED_ENTITY = + new RelatedEntityLink<>("to_TransitiveEntity"); + + @Nonnull + @Override + protected Map getKey() + { + return ImmutableMap.of("RelatedId", getRelatedId()); + } + + @Nonnull + @Override + protected Map toMapOfNavigationProperties() + { + return Collections.singletonMap("to_TransitiveEntity", transitiveEntity); + } + + public List fetchTransitiveEntity() + { + return fetchFieldAsList("to_TransitiveEntity", TransitiveEntity.class); + } + } + + @Data + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TransitiveEntity extends VdmEntity + { + private final String entityCollection = "A_TransitiveEntitySet"; + private final Class type = TransitiveEntity.class; + } + + @Data + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class MediaEntity extends VdmMediaEntity + { + private final String entityCollection = "A_MediaEntitySet"; + private final Class type = MediaEntity.class; + + @SerializedName( "MediaId" ) + @JsonProperty( "MediaId" ) + @ODataField( odataName = "MediaId" ) + private String mediaId; + + } + + public static class TestEntityLink> + extends + EntityLink, TestEntity, ObjectT> + { + public TestEntityLink( final String name ) + { + super(name); + } + } + + public static class RelatedEntityLink> + extends + EntityLink, TestEntity, ObjectT> + { + public RelatedEntityLink( final String name ) + { + super(name); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.java new file mode 100644 index 0000000000..1d93574ada --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.java @@ -0,0 +1,252 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.allRequests; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.ImmutableMap; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@WireMockTest +class SpecialCharactersTest +{ + private static final String SERVICE_URL = "/service/path"; + + private DefaultHttpDestination destination; + + @BeforeEach + void before( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + public static class Ticket1 extends VdmEntity + { + private final String entityCollection = "Tickets"; + private final Class type = Ticket1.class; + private String keyVal1 = "Val1"; + @Getter( lazy = true ) + private final Map key = ImmutableMap.of("Key1", keyVal1); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + public static class Ticket2 extends VdmEntity + { + private final String entityCollection = "Tickets"; + private final Class type = Ticket2.class; + private String keyVal1 = "Val1"; + private String keyVal2 = "Val2"; + @Getter( lazy = true ) + private final Map key = ImmutableMap.of("Key1", keyVal1, "Key2", keyVal2); + } + + @Test + void testGetByKey() + { + final String keyVal1 = "F?oo"; + final String keyVal2 = "A/ #"; + final String ticketsUrl = SERVICE_URL + "/Tickets(Key1='F%3Foo',Key2='A%2F%20%23')"; + stubFor(get(urlEqualTo(ticketsUrl)).willReturn(okJson("{\"d\":{\"Foo\":\"0\"}}"))); + + final Map key = ImmutableMap.of("Key1", keyVal1, "Key2", keyVal2); + final Ticket2 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .readByKey(Ticket2.class, "Tickets", key) + .executeRequest(destination); + + assertThat(item).isNotNull(); + verify(1, allRequests()); + verify(1, getRequestedFor(urlEqualTo(ticketsUrl))); + } + + @Test + void testGetNavigationPropertyByKey() + { + final String ticketsUrl = SERVICE_URL + "/Tickets(Key1='s4L3s%2F%200rd3%3Fr',Key2='1t3%23m')/to_NextTicket"; + + stubFor(get(urlEqualTo(ticketsUrl)).willReturn(okJson("{\"d\":{\"\":\"\"}}"))); + + final Ticket2 item = new Ticket2(); + item.setKeyVal1("s4L3s/ 0rd3?r"); + item.setKeyVal2("1t3#m"); + + item.setDestinationForFetch(destination); + item.setServicePathForFetch(SERVICE_URL); + final Ticket2 nextTicket = item.fetchFieldAsSingle("to_NextTicket", Ticket2.class); + + assertThat(nextTicket).isNotNull(); + assertThat(nextTicket.getDestinationForFetch()).isEqualTo(destination); + verify(1, allRequests()); + verify(1, getRequestedFor(urlEqualTo(ticketsUrl))); + } + + @Test + void testFilterExpression() + { + final String field1Val = "&?:/"; + final String field2Val = "#/\\"; + + final String ticketsUrl = + SERVICE_URL + "/Tickets?$filter=(Field1%20eq%20'%26%3F:/')%20and%20(Field2%20eq%20'%23/%5C')"; + + stubFor(get(urlEqualTo(ticketsUrl)).willReturn(okJson("{\"d\":{\"results\":[{\"Foo\":\"0\"}]}}"))); + + final EntityField field1 = new EntityField<>("Field1"); + final EntityField field2 = new EntityField<>("Field2"); + final Ticket2 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .read(Ticket2.class, "Tickets") + .filter(field1.eq(field1Val).and(field2.eq(field2Val))) + .executeRequest(destination) + .get(0); + + assertThat(item).isNotNull(); + verify(1, allRequests()); + verify(1, getRequestedFor(urlEqualTo(ticketsUrl))); + } + + @Test + void testEscapedNestedSingleQuote() + { + // test escaping for get by filter + { + final String getByFilter = "/Tickets?$filter=Field1%20eq%20'foo''bar'"; + stubFor( + get(urlEqualTo(SERVICE_URL + getByFilter)) + .willReturn(okJson("{\"d\":{\"results\":[{\"Foo\":\"foo'bar\"}]}}"))); + + final EntityField field1 = new EntityField<>("Field1"); + final Ticket1 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .read(Ticket1.class, "Tickets") + .filter(field1.eq("foo'bar")) + .executeRequest(destination) + .get(0); + assertThat(item).isNotNull(); + verify(1, getRequestedFor(urlEqualTo(SERVICE_URL + getByFilter))); + } + + // test escaping for get by key + { + final String getByKey = "/Tickets('foo''bar')"; + stubFor(get(urlEqualTo(SERVICE_URL + getByKey)).willReturn(okJson("{\"d\":{\"Foo\":\"foo'bar\"}}"))); + + final Map key = ImmutableMap.of("Field1", "foo'bar"); + final Ticket1 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .readByKey(Ticket1.class, "Tickets", key) + .executeRequest(destination); + assertThat(item).isNotNull(); + verify(1, getRequestedFor(urlEqualTo(SERVICE_URL + getByKey))); + } + + // test escaping for lazily fetching a navigation property + { + final String getByFilter = "/Tickets('foo''bar')/to_NextTicket"; + stubFor( + get(urlEqualTo(SERVICE_URL + getByFilter)) + .willReturn(okJson("{\"d\":{\"results\":[{\"Foo\":\"0\"}]}}"))); + final Ticket1 item = new Ticket1(); + item.setKeyVal1("foo'bar"); + + item.setDestinationForFetch(destination); + item.setServicePathForFetch(SERVICE_URL); + final Ticket1 nextTicket = item.fetchFieldAsSingle("to_NextTicket", Ticket1.class); + + assertThat(nextTicket).isNotNull(); + assertThat(nextTicket.getDestinationForFetch()).isEqualTo(destination); + verify(1, getRequestedFor(urlEqualTo(SERVICE_URL + getByFilter))); + } + + } + + @Test + void testEscapedOnlySingleQuote() + { + final String oderKey = "'"; + final String itemKey = "'"; + stubFor(head(urlEqualTo(SERVICE_URL)).willReturn(ok())); + + // test escaping for get by filter + { + final String getByFilter = "/Tickets?$filter=(Field1%20eq%20'''')%20and%20(Field2%20eq%20'''')"; + stubFor( + get(urlEqualTo(SERVICE_URL + getByFilter)) + .willReturn(okJson("{\"d\":{\"results\":[{\"Foo\":\"0\"}]}}"))); + + final EntityField field1 = new EntityField<>("Field1"); + final EntityField field2 = new EntityField<>("Field2"); + final Ticket2 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .read(Ticket2.class, "Tickets") + .filter(field1.eq(oderKey).and(field2.eq(itemKey))) + .executeRequest(destination) + .get(0); + + assertThat(item).isNotNull(); + verify(1, getRequestedFor(urlEqualTo(SERVICE_URL + getByFilter))); + } + + // test escaping for lazily fetching a navigation property + { + final String getByFilter = "/Tickets(Key1='''',Key2='''')/to_NextTicket"; + stubFor( + get(urlEqualTo(SERVICE_URL + getByFilter)) + .willReturn(okJson("{\"d\":{\"results\":[{\"Foo\":\"0\"}]}}"))); + final Ticket2 item = new Ticket2(); + item.setKeyVal1(oderKey); + item.setKeyVal2(itemKey); + + item.setDestinationForFetch(destination); + item.setServicePathForFetch(SERVICE_URL); + final Ticket2 nextTicket = item.fetchFieldAsSingle("to_NextTicket", Ticket2.class); + + assertThat(nextTicket).isNotNull(); + assertThat(nextTicket.getDestinationForFetch()).isEqualTo(destination); + verify(1, getRequestedFor(urlEqualTo(SERVICE_URL + getByFilter))); + } + + // test escaping for get by key + { + final String getByKey = "/Tickets(Key1='''',Key2='''')"; + stubFor(get(urlEqualTo(SERVICE_URL + getByKey)).willReturn(okJson("{\"d\":{\"Foo\":\"0\"}}"))); + + final Map key = ImmutableMap.of("Key1", oderKey, "Key2", itemKey); + final Ticket2 item = + FluentHelperFactory + .withServicePath(SERVICE_URL) + .readByKey(Ticket2.class, "Tickets", key) + .executeRequest(destination); + assertThat(item).isNotNull(); + verify(getRequestedFor(urlEqualTo(SERVICE_URL + getByKey))); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.java new file mode 100644 index 0000000000..1169888fc1 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.java @@ -0,0 +1,79 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) +public class TestVdmComplex extends VdmComplex +{ + @SerializedName( "SomeValue" ) + @JsonProperty( "SomeValue" ) + @Nullable + @ODataField( odataName = "SomeValue" ) + private String someValue; + + @SerializedName( "OtherValue" ) + @JsonProperty( "OtherValue" ) + @Nullable + @ODataField( odataName = "OtherValue" ) + private String otherValue; + + @SerializedName( "ComplexValue" ) + @JsonProperty( "ComplexValue" ) + @Nullable + @ODataField( odataName = "ComplexValue" ) + private TestVdmComplex complexValue; + + @Getter + private final Class type = TestVdmComplex.class; + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("SomeValue", getSomeValue()); + values.put("OtherValue", getOtherValue()); + values.put("ComplexValue", getComplexValue()); + return values; + } + + public void setSomeValue( String value ) + { + rememberChangedField("SomeValue", someValue); + someValue = value; + } + + public void setOtherValue( String value ) + { + rememberChangedField("OtherValue", otherValue); + otherValue = value; + } + + public void setComplexValue( TestVdmComplex value ) + { + rememberChangedField("ComplexValue", complexValue); + complexValue = value; + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.java new file mode 100644 index 0000000000..fc79b7fa18 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.java @@ -0,0 +1,298 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Lists; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.annotation.Key; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) +public class TestVdmEntity extends VdmEntity +{ + @Getter + final String entityCollection = "Entities"; + + @Getter + private final String defaultServicePath = "/service"; + + @Getter + private final Class type = TestVdmEntity.class; + + @Key + @SerializedName( "IntegerValue" ) + @JsonProperty( "IntegerValue" ) + @Nullable + @ODataField( odataName = "IntegerValue" ) + private Integer integerValue; + + @SerializedName( "GuidValue" ) + @JsonProperty( "GuidValue" ) + @Nullable + @ODataField( odataName = "GuidValue" ) + private UUID guidValue; + + @SerializedName( "StringValue" ) + @JsonProperty( "StringValue" ) + @Nullable + @ODataField( odataName = "StringValue" ) + private String stringValue; + + @SerializedName( "OffsetDateTimeValue" ) + @JsonProperty( "OffsetDateTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonZonedDateTimeSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonZonedDateTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeAdapter.class ) + @ODataField( + odataName = "OffsetDateTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ZonedDateTimeCalendarConverter.class ) + private ZonedDateTime offsetDateTimeValue; + + @SerializedName( "to_Parent" ) + @JsonProperty( "to_Parent" ) + @ODataField( odataName = "to_Parent" ) + @Nullable + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private TestVdmEntity toParent; + + @SerializedName( "to_Children" ) + @JsonProperty( "to_Children" ) + @ODataField( odataName = "to_Children" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toChildren; + + @SerializedName( "DecimalValue" ) + @JsonProperty( "DecimalValue" ) + @Nullable + @ODataField( odataName = "DecimalValue" ) + private BigDecimal decimalValue; + + @SerializedName( "DoubleValue" ) + @JsonProperty( "DoubleValue" ) + @Nullable + @ODataField( odataName = "DoubleValue" ) + private Double doubleValue; + + @SerializedName( "LocalTimeValue" ) + @JsonProperty( "LocalTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalTimeSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalTimeAdapter.class ) + @ODataField( + odataName = "LocalTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalTimeCalendarConverter.class ) + private LocalTime localTimeValue; + + @SerializedName( "LocalDateTimeValue" ) + @JsonProperty( "LocalDateTimeValue" ) + @Nullable + @JsonSerialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalDateTimeSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.JacksonLocalDateTimeDeserializer.class ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeAdapter.class ) + @ODataField( + odataName = "LocalDateTimeValue", + converter = com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.LocalDateTimeCalendarConverter.class ) + private LocalDateTime localDateTimeValue; + + @SerializedName( "BooleanValue" ) + @JsonProperty( "BooleanValue" ) + @Nullable + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataBooleanAdapter.class ) + @ODataField( odataName = "BooleanValue" ) + private Boolean booleanValue; + + @SerializedName( "ComplexValue" ) + @JsonProperty( "ComplexValue" ) + @Nullable + @ODataField( odataName = "ComplexValue" ) + private TestVdmComplex complexValue; + + public void setIntegerValue( @Nullable final Integer integerValue ) + { + rememberChangedField("IntegerValue", this.integerValue); + this.integerValue = integerValue; + } + + public void setBooleanValue( @Nullable final Boolean booleanValue ) + { + rememberChangedField("BooleanValue", this.booleanValue); + this.booleanValue = booleanValue; + } + + public void setGuidValue( @Nullable final UUID guidValue ) + { + rememberChangedField("GuidValue", this.guidValue); + this.guidValue = guidValue; + } + + public void setStringValue( @Nullable final String stringValue ) + { + rememberChangedField("StringValue", this.stringValue); + this.stringValue = stringValue; + } + + public void setDecimalValue( @Nullable final BigDecimal decimalValue ) + { + rememberChangedField("DecimalValue", this.decimalValue); + this.decimalValue = decimalValue; + } + + public void setDoubleValue( @Nullable final Double doubleValue ) + { + rememberChangedField("DoubleValue", this.doubleValue); + this.doubleValue = doubleValue; + } + + public void setOffsetDateTimeValue( @Nullable final ZonedDateTime offsetDateTimeValue ) + { + rememberChangedField("OffsetDateTimeValue", this.offsetDateTimeValue); + this.offsetDateTimeValue = offsetDateTimeValue; + } + + public void setLocalTimeValue( @Nullable final LocalTime localTimeValue ) + { + rememberChangedField("LocalTimeValue", this.localTimeValue); + this.localTimeValue = localTimeValue; + } + + public void setLocalDateTimeValue( @Nullable final LocalDateTime localDateTimeValue ) + { + rememberChangedField("LocalDateTimeValue", this.localDateTimeValue); + this.localDateTimeValue = localDateTimeValue; + } + + public void setComplexValue( @Nullable final TestVdmComplex complexValue ) + { + rememberChangedField("ComplexValue", this.complexValue); + this.complexValue = complexValue; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("IntegerValue", getIntegerValue()); + values.put("BooleanValue", getBooleanValue()); + values.put("StringValue", getStringValue()); + values.put("DecimalValue", getDecimalValue()); + values.put("DoubleValue", getDoubleValue()); + values.put("GuidValue", getGuidValue()); + values.put("OffsetDateTimeValue", getOffsetDateTimeValue()); + values.put("LocalDateTimeValue", getLocalDateTimeValue()); + values.put("LocalTimeValue", getLocalTimeValue()); + values.put("ComplexValue", getComplexValue()); + return values; + } + + @Nullable + public List fetchToChildren() + { + // not implemented here + return null; + } + + @Nonnull + public List getToChildrenOrFetch() + { + if( toChildren == null ) { + toChildren = fetchToChildren(); + } + return toChildren; + } + + @Nonnull + public Option> getToChildrenIfPresent() + { + return Option.of(toChildren); + } + + public void setToChildren( @Nonnull final List value ) + { + // rememberChangedField("to_Children", toChildren); + if( toChildren == null ) { + toChildren = Lists.newArrayList(); + } + toChildren.clear(); + toChildren.addAll(value); + } + + public void addToChildren( TestVdmEntity... entity ) + { + // rememberChangedField("to_Children", toChildren); + if( toChildren == null ) { + toChildren = Lists.newArrayList(); + } + toChildren.addAll(Lists.newArrayList(entity)); + } + + @Nullable + public TestVdmEntity fetchToParent() + { + // not implemented here + return null; + } + + @Nonnull + public TestVdmEntity getToParentOrFetch() + { + if( toParent == null ) { + toParent = fetchToParent(); + } + return toParent; + } + + @Nonnull + public Option getToParentIfPresent() + { + return Option.of(toParent); + } + + public void setToParent( @Nonnull final TestVdmEntity value ) + { + // rememberChangedField("to_Children", toChildren); + toParent = value; + } + + @Nonnull + @Override + protected Map getKey() + { + return Collections.singletonMap("IntegerValue", integerValue); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.java new file mode 100644 index 0000000000..6b8d4db145 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.java @@ -0,0 +1,86 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; + +import lombok.Getter; + +class UncheckedFilterExpressionTest +{ + @Test + void testClientFilterExpressionEmpty() + { + final ValueBoolean filterExpression = ValueBoolean.literal(true); + final ExpressionFluentHelper customFilterExpression = new ExpressionFluentHelper<>(filterExpression); + + assertThat( + customFilterExpression.getDelegateExpressionWithoutOuterParentheses().getExpression(ODataProtocol.V2)) + .isEqualTo("true"); + } + + @Test + void testClientFilterExpressionInteger() + { + final ValueBoolean filterExpression = FieldReference.of("ShoeSize").equalTo(42); + final ExpressionFluentHelper customFilterExpression = new ExpressionFluentHelper<>(filterExpression); + + assertThat( + customFilterExpression.getDelegateExpressionWithoutOuterParentheses().getExpression(ODataProtocol.V2)) + .isEqualTo("ShoeSize eq 42"); + + final ODataRequestRead read = newFluentHelperRead().filter(customFilterExpression).toRequest(); + assertThat(read.getRelativeUri()).hasQuery("$filter=ShoeSize eq 42"); + } + + @Test + void testClientFilterExpressionString() + { + final ValueBoolean filterExpression = FieldReference.of("FirstName").equalTo("Alice"); + final ExpressionFluentHelper customFilterExpression = new ExpressionFluentHelper<>(filterExpression); + + assertThat( + customFilterExpression.getDelegateExpressionWithoutOuterParentheses().getExpression(ODataProtocol.V2)) + .isEqualTo("FirstName eq 'Alice'"); + + final ODataRequestRead read = newFluentHelperRead().filter(customFilterExpression).toRequest(); + assertThat(read.getRelativeUri()).hasQuery("$filter=FirstName eq 'Alice'"); + } + + // test helper classes + + private static < + T extends FluentHelperRead> + FluentHelperRead + newFluentHelperRead() + { + return FluentHelperFactory.withServicePath("some/path").read(MyEntity.class, "MyEntityCollection"); + } + + static class MyEntity extends VdmEntity + { + @Getter + private final String entityCollection = "MyEntityCollection"; + + @Getter + private final String defaultServicePath = "API_MY_ENTITY"; + + @Nonnull + @Override + public Class getType() + { + return MyEntity.class; + } + } + + static class MySelectable + { + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.java new file mode 100644 index 0000000000..0872b98ede --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.java @@ -0,0 +1,96 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; + +@WireMockTest +class VdmComplexTest +{ + private static final String RESPONSE_CREATE_ENTITY = """ + {"d":{ + "__metadata": { + "id": "https://127.0.0.1/path/to/service(100)", + "uri": "https://127.0.0.1/path/to/service(100)", + "type": "SERVICE.SomeEntity" + }, + "IntegerValue":100, + "StringValue":"Foo", + "ComplexValue":{ + "__metadata": { + "type": "SERVICE.SomeComplex" + }, + "SomeValue":"Some", + "OtherValue":"Another" + } + }} + """; + + private static final String REQUEST_CREATE_ENTITY = """ + { + "StringValue" : "Foo", + "ComplexValue" : { + "SomeValue" : "Some", + "OtherValue" : "Another" + } + } + """; + + private HttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Test + void testCreateEntityWithComplexProperty() + { + stubFor(head(anyUrl()).willReturn(noContent())); + stubFor(post(anyUrl()).willReturn(okJson(RESPONSE_CREATE_ENTITY))); + + final TestVdmComplex complex = new TestVdmComplex(); + complex.setSomeValue("Some"); + complex.setOtherValue("Another"); + + final TestVdmEntity entity = new TestVdmEntity(); + entity.setStringValue("Foo"); + entity.setComplexValue(complex); + + final TestVdmEntity resultEntity = + FluentHelperFactory + .withServicePath("/path/to/service") + .create(entity.getEntityCollection(), entity) + .executeRequest(destination) + .getModifiedEntity(); + + assertThat(resultEntity).isNotNull().isNotEqualTo(entity); + assertThat(resultEntity.getComplexValue()).isEqualTo(complex); + + verify(headRequestedFor(urlPathEqualTo("/path/to/service/")).withHeader("x-csrf-token", equalTo("fetch"))); + verify( + postRequestedFor(urlPathEqualTo("/path/to/service/Entities")) + .withRequestBody(equalToJson(REQUEST_CREATE_ENTITY))); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.java new file mode 100644 index 0000000000..9ad5073129 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.java @@ -0,0 +1,162 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationProperty; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestinationProperties; + +class VdmEntityTest +{ + private static final String TEST_DEFAULT_SERVICE_PATH = "/odata/default"; + private static final String TEST_DESTINATION_NAME = "UnitTestDestination"; + + private static HttpDestinationProperties TEST_DESTINATION; + + private static class TestEntity extends VdmEntity + { + @Override + protected String getEntityCollection() + { + return null; + } + + @Nonnull + @Override + public Class getType() + { + return TestEntity.class; + } + + @Override + protected String getDefaultServicePath() + { + return TEST_DEFAULT_SERVICE_PATH; + } + } + + @BeforeAll + static void setUp() + { + TEST_DESTINATION = DefaultHttpDestination.builder("").name(TEST_DESTINATION_NAME).build(); + } + + @Test + void testAttachToErpWithDefaults() + { + final TestEntity sut = new TestEntity(); + sut.attachToService(null, (HttpDestinationProperties) null); + + assertThat(sut.getServicePathForFetch()).isNotEmpty(); + assertThat(sut.getServicePathForFetch()).isEqualTo(TEST_DEFAULT_SERVICE_PATH); + + assertThat(sut.getDestinationForFetch()).isNull(); + } + + @Test + void testAttachToErpWithCustomErpConfigContext() + { + final TestEntity sut = new TestEntity(); + sut.attachToService(null, TEST_DESTINATION); + + assertThat(sut.getServicePathForFetch()).isNotEmpty(); + assertThat(sut.getServicePathForFetch()).isEqualTo(TEST_DEFAULT_SERVICE_PATH); + + assertThat(sut.getDestinationForFetch()).isNotNull(); + assertThat(sut.getDestinationForFetch().get(DestinationProperty.NAME)).contains(TEST_DESTINATION_NAME); + } + + @Test + void testAttachToErpWithCustomServicePath() + { + final TestEntity sut = new TestEntity(); + sut.attachToService("/sap/opu/odata", (HttpDestinationProperties) null); + + assertThat(sut.getServicePathForFetch()).isNotEmpty(); + assertThat(sut.getServicePathForFetch()).isEqualTo("/sap/opu/odata"); + + assertThat(sut.getDestinationForFetch()).isNull(); + } + + @Test + void testAttachToErpWithAllCustomParameters() + { + final TestEntity sut = new TestEntity(); + sut.attachToService("/sap/opu/odata", TEST_DESTINATION); + + assertThat(sut.getServicePathForFetch()).isNotEmpty(); + assertThat(sut.getServicePathForFetch()).isEqualTo("/sap/opu/odata"); + + assertThat(sut.getDestinationForFetch()).isNotNull(); + assertThat(sut.getDestinationForFetch().get(DestinationProperty.NAME)).contains(TEST_DESTINATION_NAME); + } + + @Test + void testEntityComparison() + { + final TestVdmEntity foo1 = TestVdmEntity.builder().stringValue("foo").build(); + final TestVdmEntity foo2 = TestVdmEntity.builder().stringValue("foo").build(); + assertThat(foo1) + .withFailMessage( + "Entities wit equal properties should be equal. Expected:\n %s\n to be equal to:\n %s\nbut was not.", + foo1, + foo2) + .isEqualTo(foo2); + + foo1.setVersionIdentifier("1"); + foo2.setVersionIdentifier("1"); + assertThat(foo1).withFailMessage("Equal entities with equal ETags should be equal.").isEqualTo(foo2); + + foo1.setServicePathForFetch("bar"); + foo2.setServicePathForFetch("baz"); + assertThat(foo1) + .withFailMessage("Equal entities with different service paths should be equal.") + .isEqualTo(foo2); + + foo1.setDestinationForFetch(DefaultHttpDestination.builder("bar").build()); + foo2.setDestinationForFetch(DefaultHttpDestination.builder("baz").build()); + assertThat(foo1) + .withFailMessage("Equal entities with different service paths should be equal.") + .isEqualTo(foo2); + + foo2.setVersionIdentifier("2"); + assertThat(foo1).withFailMessage("Equal entities with different ETags should not be equal.").isNotEqualTo(foo2); + } + + @Test + void testChangedNonCustomFields() + { + final TestVdmEntity entity = TestVdmEntity.builder().stringValue("old").build(); + + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setStringValue("old"); + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setStringValue("new"); + assertThat(entity.getChangedFields()).containsOnlyKeys("StringValue"); + } + + @Test + void testChangedCustomFields() + { + final TestVdmEntity entity = TestVdmEntity.builder().build(); + + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setCustomField("foo", "bar"); + assertThat(entity.getChangedFields()).containsOnlyKeys("foo"); + + entity.resetChangedFields(); + entity.setCustomField("foo", "bar"); + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setCustomField("foo", "baz"); + assertThat(entity.getChangedFields()).containsOnlyKeys("foo"); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.java new file mode 100644 index 0000000000..d86d22ed62 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.java @@ -0,0 +1,354 @@ +package com.sap.cloud.sdk.datamodel.odata.helper; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataResponseException; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataField; +import com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.With; + +class VdmEntityVersionIdentifierTest +{ + @RegisterExtension + static final WireMockExtension ERP_SERVER = + WireMockExtension.newInstance().options(wireMockConfig().dynamicPort().gzipDisabled(true)).build(); + + private static final String key1val = "2015"; + private static final String key2val = "100000000"; + private static final String key3val = "1"; + private static final Map KEY_MAP = + ImmutableMap.of("Key1", key1val, "Key2", key2val, "Key3", key3val); + + private static final String versionIdentifierInResponseBody = + "W/\"datetimeoffset'2018-01-09T08%3A33%3A53.8828600Z'\""; + private static final String versionIdentifierInHeader = "W/\"datetimeoffset'2015-01-09T08%3A33%3A53.8828600Z'\""; + private static final String updatedVersionIdentifier = "W/\"datetimeoffset'2015-01-09T08%3A33%3A53.8828600Z'\""; + + private static final String ODATA_ENDPOINT_URL = "/path/to/service"; + private static final String ODATA_COLLECTION = "A_TestEntity"; + private static final String ODATA_DOCUMENT_ITEM_URL = + String + .format( + "%s/%s(Key1='%s',Key2='%s',Key3='%s')", + ODATA_ENDPOINT_URL, + ODATA_COLLECTION, + key1val, + key2val, + key3val); + + private static final String getDocumentItemResponseBody = """ + { + "d": { + "__metadata": { + "id": "https://127.0.0.1/path/to/service/A_TestEntity(Key1='2018',Key2='100010641',Key3='1')", + "uri": "https://127.0.0.1/path/to/service/A_TestEntity(Key1='2018',Key2='100010641',Key3='1')", + "type": "SERVICE.A_TestEntityType", + "etag": "W/\\"datetimeoffset'2018-01-09T08%3A33%3A53.8828600Z'\\"" + }, + "Key1": "2015", + "Key2": "100000000", + "Key3": "1", + "SomeField": "Foo" + } + } + """; + + @Data + @NoArgsConstructor + @AllArgsConstructor( access = AccessLevel.PRIVATE ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( ODataVdmEntityAdapterFactory.class ) + public static class TestEntity extends VdmEntity + { + private final String entityCollection = ODATA_COLLECTION; + private final Class type = TestEntity.class; + + @With + @SerializedName( "Key1" ) + @JsonProperty( "Key1" ) + @ODataField( odataName = "Key1" ) + private String key1; + + @With + @SerializedName( "Key2" ) + @JsonProperty( "Key2" ) + @ODataField( odataName = "Key2" ) + private String key2; + + @With + @SerializedName( "Key3" ) + @JsonProperty( "Key3" ) + @ODataField( odataName = "Key3" ) + private String key3; + + @With + @SerializedName( "SomeField" ) + @JsonProperty( "SomeField" ) + @ODataField( odataName = "SomeField" ) + private String someField; + + @Nonnull + @Override + protected Map getKey() + { + return ImmutableMap.of("Key1", getKey1(), "Key2", getKey2(), "Key3", getKey3()); + } + + public void setSomeField( final String someField ) + { + rememberChangedField("SomeField", this.someField); + this.someField = someField; + } + } + + private DefaultHttpDestination destination; + + @BeforeEach + void before() + { + destination = DefaultHttpDestination.builder(ERP_SERVER.baseUrl()).build(); + ERP_SERVER.stubFor(head(urlEqualTo(ODATA_ENDPOINT_URL)).willReturn(WireMock.ok())); + ERP_SERVER + .stubFor( + get(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(aResponse().withBody(getDocumentItemResponseBody))); + } + + @Test + void testVersionIdentifier() + { + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(ok())); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInResponseBody)); + + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .executeRequest(destination); + + ERP_SERVER + .verify( + patchRequestedFor(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)) + .withHeader("If-Match", equalTo(versionIdentifierInResponseBody))); + } + + @Test + void testVersionIdentifierSetInHeader() + { + ERP_SERVER + .stubFor( + get(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)) + .willReturn( + aResponse() + .withBody(getDocumentItemResponseBody) + .withHeader("ETag", versionIdentifierInHeader))); + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(ok())); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInHeader)); + + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .executeRequest(destination); + + ERP_SERVER + .verify( + patchRequestedFor(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)) + .withHeader("If-Match", equalTo(versionIdentifierInHeader))); + } + + @Test + void testIgnoreVersionIdentifier() + { + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(ok())); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInResponseBody)); + + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .matchAnyVersionIdentifier() + .executeRequest(destination); + + ERP_SERVER.verify(patchRequestedFor(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).withHeader("If-Match", equalTo("*"))); + } + + @Test + void testIgnoreVersionIdentifierEvenIfNoVersionIdentifierPresent() + { + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(ok())); + + final TestEntity item = new TestEntity().withKey1(key1val).withKey2(key2val).withKey3(key3val); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier().isDefined()).isFalse(); + + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .matchAnyVersionIdentifier() + .executeRequest(destination); + + ERP_SERVER.verify(patchRequestedFor(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).withHeader("If-Match", equalTo("*"))); + } + + @Test + void testExceptionWhenExpiredVersionIdentifierIsSent() + { + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(aResponse().withStatus(412))); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInResponseBody)); + + item.setVersionIdentifier(updatedVersionIdentifier); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(updatedVersionIdentifier)); + + assertThatThrownBy( + () -> FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .executeRequest(destination)) + .isInstanceOf(ODataResponseException.class) + .extracting("httpCode") + .isEqualTo(412); + } + + @Test + void testExceptionWhenVersionIdentifierMissing() + { + ERP_SERVER.stubFor(patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(aResponse().withStatus(428))); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInResponseBody)); + + item.setVersionIdentifier(updatedVersionIdentifier); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(updatedVersionIdentifier)); + + assertThatThrownBy( + () -> FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .executeRequest(destination)) + .isInstanceOf(ODataResponseException.class) + .extracting("httpCode") + .isEqualTo(428); + } + + @Test + void testVersionIdentifierSentAndFetchedEvenAfterUpdate() + { + ERP_SERVER + .stubFor( + get(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)).willReturn(aResponse().withBody(getDocumentItemResponseBody))); + ERP_SERVER + .stubFor( + patch(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)) + .willReturn(ok().withHeader("ETag", versionIdentifierInHeader))); + + final TestEntity item = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .readByKey(TestEntity.class, ODATA_COLLECTION, KEY_MAP) + .executeRequest(destination); + + item.setKey1("2015"); + item.setSomeField("Bar"); + + assertThat(item.getVersionIdentifier()).isEqualTo(Option.of(versionIdentifierInResponseBody)); + + final ModificationResponse updateResponse = + FluentHelperFactory + .withServicePath(ODATA_ENDPOINT_URL) + .update(ODATA_COLLECTION, item) + .executeRequest(destination); + + ERP_SERVER + .verify( + patchRequestedFor(urlEqualTo(ODATA_DOCUMENT_ITEM_URL)) + .withHeader("If-Match", equalTo(versionIdentifierInResponseBody))); + + assertThat(updateResponse.getRequestEntity().getVersionIdentifier()) + .isEqualTo(Option.of(versionIdentifierInResponseBody)); + assertThat(updateResponse.getModifiedEntity().getVersionIdentifier()) + .isEqualTo(Option.of(versionIdentifierInHeader)); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.java new file mode 100644 index 0000000000..29a9e15a47 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.java @@ -0,0 +1,251 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToIgnoreCase; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odata.helper.TestVdmEntity; + +import lombok.SneakyThrows; + +@WireMockTest +class ODataV2BatchFunctionImportTest +{ + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_FETCH_VALUE = "fetch"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + private static final String REQUEST_URL_BATCH = "/$batch"; + private static final String RESPONSE_BODY = readResourceFileCrlf("BatchResponse.txt"); + private static final String REQUEST_BODY_POST = readResourceFileCrlf("BatchRequestFunctionImportWithPost.txt"); + private static final String REQUEST_BODY_GET = readResourceFileCrlf("BatchRequestFunctionImportWithGet.txt"); + private static final String REQUEST_BODY_POST_WITH_CUSTOM_HEADER = + readResourceFileCrlf("BatchRequestFunctionImportWithPostWithCustomHeader.txt"); + + @BeforeEach + void before() + { + stubFor( + head(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase(X_CSRF_TOKEN_HEADER_FETCH_VALUE)) + .willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + + final String contentType = "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + stubFor(post(urlEqualTo(REQUEST_URL_BATCH)).willReturn(okForContentType(contentType, RESPONSE_BODY))); + } + + @Test + void testIllegalStateExceptionWhenFunctionImportUsingHttpGetInChangeSet( @Nonnull final WireMockRuntimeInfo wm ) + { + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet functionImport = + new TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet("John", "Doe"); + + assertThatIllegalStateException().isThrownBy(() -> { + new TestVdmEntityBatch("") + .beginChangeSet() + .addFunctionImport(functionImport) + .endChangeSet() + .executeRequest(destination); + }); + } + + @Test + void testFunctionImportUsingHttpPostInChangeSet( @Nonnull final WireMockRuntimeInfo wm ) + { + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final TestVdmEntityBatch.TestFunctionImportHttpPost functionImport = + new TestVdmEntityBatch.TestFunctionImportHttpPost("John", "Doe"); + + new TestVdmEntityBatch("") + .beginChangeSet() + .addFunctionImport(functionImport) + .endChangeSet() + .executeRequest(destination); + + verify(postRequestedFor(urlPathEqualTo(REQUEST_URL_BATCH)).withRequestBody(equalTo(REQUEST_BODY_POST))); + } + + @Test + void testFunctionImportUsingHttpGetInBatchRequest( @Nonnull final WireMockRuntimeInfo wm ) + { + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet functionImport = + new TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet("John", "Doe"); + + new TestVdmEntityBatch("").addReadOperations(functionImport).executeRequest(destination); + + verify(postRequestedFor(urlPathEqualTo(REQUEST_URL_BATCH)).withRequestBody(equalTo(REQUEST_BODY_GET))); + } + + @Test + void + testIllegalStateExceptionWhenFunctionImportUsingHttpPostInReadOperation( @Nonnull final WireMockRuntimeInfo wm ) + { + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final TestVdmEntityBatch.TestFunctionImportHttpPost functionImport = + new TestVdmEntityBatch.TestFunctionImportHttpPost("John", "Doe"); + + assertThatIllegalStateException().isThrownBy(() -> { + new TestVdmEntityBatch("").addReadOperations(functionImport).executeRequest(destination); + }); + } + + @Test + void testFunctionImportUsingHttpPostInChangeSetWithCustomHeader( @Nonnull final WireMockRuntimeInfo wm ) + { + final DefaultHttpDestination destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + final TestVdmEntityBatch.TestFunctionImportHttpPost functionImport = + new TestVdmEntityBatch.TestFunctionImportHttpPost("John", "Doe").withHeader("foo", "bar"); + + new TestVdmEntityBatch("") + .beginChangeSet() + .addFunctionImport(functionImport) + .endChangeSet() + .executeRequest(destination); + + verify( + postRequestedFor(urlPathEqualTo(REQUEST_URL_BATCH)) + .withRequestBody(equalTo(REQUEST_BODY_POST_WITH_CUSTOM_HEADER))); + } + + @SneakyThrows + private static String readResourceFileCrlf( final String resourceFileName ) + { + final URL resourceUrl = + ODataV2BatchRequestUnitTest.class + .getClassLoader() + .getResource(ODataV2BatchFunctionImportTest.class.getSimpleName() + "/" + resourceFileName); + final String result = Resources.toString(resourceUrl, StandardCharsets.UTF_8); + return result.replaceAll("(? "StringValue", () -> "IntegerValue") + .top(10) + .withHeader("header-read_all", "read_all"); + + final TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet testFunctionImportSingleResultHttpGet = + new TestVdmEntityBatch.TestFunctionImportSingleResultHttpGet("Alice", "Bob"); + + final TestVdmEntityBatch.TestFunctionImportCollectionResultHttpGet testFunctionImportCollectionResultHttpGet = + new TestVdmEntityBatch.TestFunctionImportCollectionResultHttpGet(); + + final TestVdmEntityBatch.TestFunctionImportHttpPost functionImport = + new TestVdmEntityBatch.TestFunctionImportHttpPost("John", "Doe"); + + final TestVdmEntityBatch.TestFunctionImportSingleEntityResultHttpGet testFunctionImportSingleEntityResultHttpGet = + new TestVdmEntityBatch.TestFunctionImportSingleEntityResultHttpGet(33, "Alice"); + + final TestVdmEntityBatch batchBuilder = + testVdmEntityBatch + .addReadOperations(readAll) + .addChangeSet(create) + .addReadOperations(entityByKey) + .addChangeSet(update, delete) + .beginChangeSet() + .create(testVdmEntity) + .addFunctionImport(functionImport) + .endChangeSet() + .addReadOperations(testFunctionImportSingleResultHttpGet, testFunctionImportCollectionResultHttpGet) + .addReadOperations(testFunctionImportSingleEntityResultHttpGet); + + final ODataRequestBatch lowLevelRequest = batchBuilder.toRequest(); + + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultMultipartGeneric lowLevelResult = lowLevelRequest.execute(httpClient); + + //Customer transition example from low-level result object to typed result + + final DefaultBatchResponseResult convertedHighLevelResult = + DefaultBatchResponseResult.of(lowLevelResult, batchBuilder); + + final BatchResponse expectedHighLevelResult = batchBuilder.executeRequest(destination); + + assertThat(expectedHighLevelResult.getReadResult(readAll)) + .isEqualTo(convertedHighLevelResult.getReadResult(readAll)); + + assertThat(expectedHighLevelResult.getReadResult(entityByKey)) + .isEqualTo(convertedHighLevelResult.getReadResult(entityByKey)); + assertThat(expectedHighLevelResult.getReadResult(entityByKey)) + .extracting(TestVdmEntity::getIntegerValue) + .isEqualTo(9000); + + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportSingleResultHttpGet)) + .isEqualTo(convertedHighLevelResult.getReadResult(testFunctionImportSingleResultHttpGet)); + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportSingleResultHttpGet)) + .isEqualTo("awesomeStuff"); + + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportCollectionResultHttpGet)) + .isEqualTo(convertedHighLevelResult.getReadResult(testFunctionImportCollectionResultHttpGet)); + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportCollectionResultHttpGet)) + .isEqualTo(Arrays.asList("foo", "bar")); + + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportSingleEntityResultHttpGet)) + .isEqualTo(convertedHighLevelResult.getReadResult(testFunctionImportSingleEntityResultHttpGet)); + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportSingleEntityResultHttpGet)) + .extracting(TestVdmEntity::getIntegerValue) + .isEqualTo(33); + assertThat(expectedHighLevelResult.getReadResult(testFunctionImportSingleEntityResultHttpGet)) + .extracting(TestVdmEntity::getStringValue) + .isEqualTo("Alice"); + + assertThat(expectedHighLevelResult.get(0).get().getCreatedEntities()) + .isEqualTo(convertedHighLevelResult.get(0).get().getCreatedEntities()); + assertThat(expectedHighLevelResult.get(1).get().getCreatedEntities()) + .isEqualTo(convertedHighLevelResult.get(1).get().getCreatedEntities()); + assertThat(expectedHighLevelResult.get(2).isFailure()).isEqualTo(convertedHighLevelResult.get(2).isFailure()); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java new file mode 100644 index 0000000000..471376142d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java @@ -0,0 +1,347 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalToIgnoreCase; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestinationProperties; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; +import com.sap.cloud.sdk.datamodel.odata.helper.TestVdmEntity; + +import lombok.SneakyThrows; +import lombok.Value; + +class ODataV2BatchRequestUnitTest +{ + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_FETCH_VALUE = "fetch"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + private static final String REQUEST_URL_BATCH = "/$batch"; + + private static final String REQUEST_BODY = readResourceFileCrlf("BatchRequest.txt"); + private static final String REQUEST_BODY_WITH_CUSTOM_HEADERS = + readResourceFileCrlf("BatchRequestWithCustomHeaders.txt"); + private static final String RESPONSE_BODY = readResourceFileCrlf("BatchResponse.txt"); + private static final int MAX_PARALLEL_CONNECTIONS = 10; + + private final WireMockServer server = new WireMockServer(WIREMOCK_CONFIGURATION); + private DefaultHttpDestination destination; + + private static Stream getTestParameters() + { + return Stream.of(new TestParameter("#executeRequest", BatchFluentHelperBasic::executeRequest, REQUEST_BODY)); + } + + private static Stream getTestParametersWithCustomHeaders() + { + return Stream + .of( + new TestParameter( + "#executeRequest", + BatchFluentHelperBasic::executeRequest, + REQUEST_BODY_WITH_CUSTOM_HEADERS)); + } + + @Value + static class TestParameter + { + String label; + Executor executor; + String requestBody; + + public String toString() + { + return label; + } + + interface Executor + { + BatchResponse execute( BatchFluentHelperBasic fluentHelper, HttpDestinationProperties destination ) + throws Exception; + } + } + + @BeforeEach + void setup() + { + server.start(); + destination = DefaultHttpDestination.builder(server.baseUrl()).build(); + + // Mock CSRF token handling + server + .stubFor( + head(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase(X_CSRF_TOKEN_HEADER_FETCH_VALUE)) + .willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + + // Mock OData Batch response + final String contentType = "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + server.stubFor(post(urlEqualTo(REQUEST_URL_BATCH)).willReturn(okForContentType(contentType, RESPONSE_BODY))); + + ApacheHttpClient5Accessor + .setHttpClientFactory( + new ApacheHttpClient5FactoryBuilder() + .maxConnectionsTotal(MAX_PARALLEL_CONNECTIONS) + .maxConnectionsPerRoute(MAX_PARALLEL_CONNECTIONS) + .build()); + } + + @AfterEach + void shutdown() + { + server.shutdown(); + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } + + @SneakyThrows + @ParameterizedTest( name = "{0}" ) + @MethodSource( "getTestParameters" ) + void testAllOperations( @Nonnull final TestParameter parameter ) + { + final TestVdmEntity entity12 = TestVdmEntity.builder().integerValue(12).build(); + final TestVdmEntity entity13 = TestVdmEntity.builder().integerValue(13).build(); + final TestVdmEntity entity14 = TestVdmEntity.builder().integerValue(14).build(); + final TestVdmEntity entity15 = TestVdmEntity.builder().integerValue(15).build(); + final TestVdmEntity entity16 = TestVdmEntity.builder().integerValue(16).build(); + + final TestVdmEntityBatch.TestEntityRead readAll = + new TestVdmEntityBatch.TestEntityRead().select(() -> "StringValue", () -> "IntegerValue").top(10); + final TestVdmEntityBatch.TestEntityByKey readByKey = + new TestVdmEntityBatch.TestEntityByKey(ImmutableMap.of("IntegerValue", 9000)); + + // prepare batched requests + final TestVdmEntityBatch request = + new TestVdmEntityBatch("") + + // read operation + .addReadOperations(readAll) + + // changeset:0 + .beginChangeSet() + .create(entity12) + .endChangeSet() + + // read operation + .addReadOperations(readByKey) + + // changeset:1 + .beginChangeSet() + .update(entity13) + .delete(entity14) + .endChangeSet() + + // changeset:2 + .beginChangeSet() + .delete(entity15) + .delete(entity16) + .endChangeSet(); + + // execute batch request with try-with-resources to ensure no connection leaks + for( int i = 0; i <= MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + try( final BatchResponse response = request.executeRequest(destination) ) { + verifyBatchChangeSets(response); + verifyBatchReadAll(response, readAll); + verifyBatchReadByKey(response, readByKey); + verifyBatchRequestBody(parameter); + } + } + } + + @SneakyThrows + @ParameterizedTest( name = "{0}" ) + @MethodSource( "getTestParametersWithCustomHeaders" ) + void testAllOperationsWithCustomHeaders( @Nonnull final TestParameter parameter ) + { + final TestVdmEntity entity12 = TestVdmEntity.builder().integerValue(12).build(); + final TestVdmEntity entity13 = TestVdmEntity.builder().integerValue(13).build(); + final TestVdmEntity entity14 = TestVdmEntity.builder().integerValue(14).build(); + final TestVdmEntity entity15 = TestVdmEntity.builder().integerValue(15).build(); + final TestVdmEntity entity16 = TestVdmEntity.builder().integerValue(16).build(); + + final TestVdmEntityBatch.TestEntityRead readAll = + new TestVdmEntityBatch.TestEntityRead() + .select(() -> "StringValue", () -> "IntegerValue") + .top(10) + .withHeader("header-read_all", "read_all"); + final TestVdmEntityBatch.TestEntityByKey readByKey = + new TestVdmEntityBatch.TestEntityByKey(ImmutableMap.of("IntegerValue", 9000)) + .withHeader("header-read_by_key", "read_by_key"); + final TestVdmEntityBatch.TestEntityCreate create = + new TestVdmEntityBatch.TestEntityCreate(entity12).withHeader("header-create", "create"); + final TestVdmEntityBatch.TestEntityUpdate update = + new TestVdmEntityBatch.TestEntityUpdate(entity13).withHeader("header-update", "update"); + final TestVdmEntityBatch.TestEntityDelete deleteEntity14 = + new TestVdmEntityBatch.TestEntityDelete(entity14).withHeader("header-delete", "delete-entity14"); + final TestVdmEntityBatch.TestEntityDelete deleteEntity15 = + new TestVdmEntityBatch.TestEntityDelete(entity15).withHeader("header-delete", "delete-entity15"); + final TestVdmEntityBatch.TestEntityDelete deleteEntity16 = + new TestVdmEntityBatch.TestEntityDelete(entity16).withHeader("header-delete", "delete-entity16"); + + // prepare batched requests + final TestVdmEntityBatch request = + new TestVdmEntityBatch("") + + // read operation + .addReadOperations(readAll) + + // changeset:0 + .addChangeSet(create) + + // read operation + .addReadOperations(readByKey) + + // changeset:1 + .addChangeSet(update, deleteEntity14) + + // changeset:2 + .addChangeSet(deleteEntity15, deleteEntity16); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + // execute batch request + final BatchResponse response = parameter.getExecutor().execute(request, destination); + + verifyBatchChangeSets(response); + verifyBatchReadAll(response, readAll); + verifyBatchReadByKey(response, readByKey); + verifyBatchRequestBody(parameter); + } + } + + private void verifyBatchChangeSets( @Nonnull final BatchResponse response ) + { + // assertion on response parsing + assertThat(response.get(0).isSuccess()).isTrue(); + assertThat(response.get(0).get().getCreatedEntities()).hasSize(1); + assertThat(response.get(1).isSuccess()).isTrue(); + assertThat(response.get(1).get().getCreatedEntities()).hasSize(0); + + assertThat(response.get(2).isSuccess()).isFalse(); // legitimately no success: unhealthy response + assertThat(response.get(2).getCause()).isInstanceOfAny(ODataServiceErrorException.class); + assertThat(response.get(3).isSuccess()).isFalse(); // index out of bounds + assertThat(response.get(3).getCause()).isInstanceOf(IllegalArgumentException.class); + } + + private void verifyBatchReadAll( + @Nonnull final BatchResponse response, + @Nonnull final TestVdmEntityBatch.TestEntityRead readAll ) + { + assertThat(response.getReadResult(readAll)) + .satisfiesExactly(item -> assertThat(item.getStringValue()).isEqualTo("Foo")); + } + + private void verifyBatchReadByKey( + @Nonnull final BatchResponse response, + @Nonnull final TestVdmEntityBatch.TestEntityByKey readByKey ) + { + assertThat(response.getReadResult(readByKey)).extracting(TestVdmEntity::getIntegerValue).isEqualTo(9000); + } + + private void verifyBatchRequestBody( @Nonnull final TestParameter parameter ) + { + server + .verify( + postRequestedFor(urlEqualTo(REQUEST_URL_BATCH)) + .withHeader("Content-Type", matching("multipart/mixed;boundary=batch_[a-z0-9-]+")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalToIgnoreCase(X_CSRF_TOKEN_HEADER_VALUE)) + .withRequestBody(matching("\\Q" + parameter.getRequestBody() + "\\E"))); + } + + @SneakyThrows + private static String readResourceFileCrlf( final String resourceFileName ) + { + final URL resourceUrl = + ODataV2BatchRequestUnitTest.class + .getClassLoader() + .getResource(ODataV2BatchRequestUnitTest.class.getSimpleName() + "/" + resourceFileName); + final String result = Resources.toString(resourceUrl, StandardCharsets.UTF_8); + return result.replaceAll("(? inputStreams = new ArrayList<>(); + + final HttpDestination dest = DefaultHttpDestination.builder("").build(); + final HttpClient httpClient = mock(HttpClient.class); + when(httpClient.executeOpen(isNull(), argThat(req -> req instanceof HttpPost), isNull())).thenAnswer(args -> { + final BasicClassicHttpResponse response = new BasicClassicHttpResponse(200, "ok"); + final InputStream inStream = mock(InputStream.class); + inputStreams.add(inStream); + response.setEntity(new InputStreamEntity(inStream, ContentType.APPLICATION_JSON)); + return response; + }); + + // configure test setup + ApacheHttpClient5Accessor.setHttpClientFactory(( anyDestination ) -> httpClient); + + // TEST: invoke many batch request each spawning an InputStream + for( int i = 0; i < N; i++ ) { + new TestVdmEntityBatch("").addReadOperations(new TestVdmEntityBatch.TestEntityRead()).executeRequest(dest); + } + + // ASSERTION: input streams are loaded but never fully consumed + assertThat(inputStreams).hasSize(N); + for( final InputStream inStream : inputStreams ) { + Mockito.verify(inStream, never()).close(); + } + + // TEST: invoke one batch request using try-with-resource + try( + BatchResponse result = + new TestVdmEntityBatch("") + .addReadOperations(new TestVdmEntityBatch.TestEntityRead()) + .executeRequest(dest) ) { + + assertThat(result).isNotNull(); + } + + // ASSERTION: input stream is fully consumed + Mockito.verify(inputStreams.get(N), times(1)).close(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.java new file mode 100644 index 0000000000..7c325d032d --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.java @@ -0,0 +1,194 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.sap.cloud.sdk.datamodel.odata.helper.batch.TestVdmEntityBatch.TestEntityByKey; +import static com.sap.cloud.sdk.datamodel.odata.helper.batch.TestVdmEntityBatch.TestEntityCreate; +import static com.sap.cloud.sdk.datamodel.odata.helper.batch.TestVdmEntityBatch.TestEntityDelete; +import static com.sap.cloud.sdk.datamodel.odata.helper.batch.TestVdmEntityBatch.TestEntityRead; +import static com.sap.cloud.sdk.datamodel.odata.helper.batch.TestVdmEntityBatch.TestEntityUpdate; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.ConnectionRequestTimeoutException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.github.tomakehurst.wiremock.matching.UrlPattern; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataConnectionException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; +import com.sap.cloud.sdk.datamodel.odata.helper.TestVdmEntity; + +import lombok.SneakyThrows; + +@WireMockTest +class ODatav2BatchConnectionTest +{ + private static final String RESPONSE_CONTENT_TYPE = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + private static final String RESPONSE_WITH_CHANGESET = readResourceFileCrlf("BatchResponseWithChangeset.txt"); + private static final String RESPONSE_WITHOUT_CHANGESET = readResourceFileCrlf("BatchResponseWithoutChangeset.txt"); + private static final String RESPONSE_WITH_ERROR = readResourceFileCrlf("BatchResponseWithError.txt"); + private static final int MAX_PARALLEL_CONNECTIONS = 10; + private DefaultHttpDestination destination; + + @SneakyThrows + private static String readResourceFileCrlf( final String resourceFileName ) + { + final URL resourceUrl = + ODatav2BatchConnectionTest.class + .getClassLoader() + .getResource(ODatav2BatchConnectionTest.class.getSimpleName() + "/" + resourceFileName); + final String result = Resources.toString(resourceUrl, StandardCharsets.UTF_8); + return result.replaceAll("(? result = batchResponse.getReadResult(read); + assertThat(result).isNotEmpty(); + final TestVdmEntity readByKeyResult = batchResponse.getReadResult(readByKey); + assertThat(readByKeyResult).extracting(TestVdmEntity::getIntegerValue).isEqualTo(9000); + } + } + + @Test + void testNoConnectionTimeoutWhenBatchResponseContainsChangeset() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITH_CHANGESET))); + + final TestEntityRead read = new TestEntityRead(); + final TestEntityByKey readByKey = new TestEntityByKey(ImmutableMap.of("IntegerValue", 9000)); + final TestEntityCreate create = new TestEntityCreate(TestVdmEntity.builder().integerValue(1337).build()); + final TestVdmEntity updateEntity = TestVdmEntity.builder().integerValue(13).build(); + final TestEntityUpdate update = new TestEntityUpdate(updateEntity); + final TestVdmEntity deleteEntity = TestVdmEntity.builder().integerValue(14).build(); + final TestEntityDelete delete = new TestEntityDelete(deleteEntity); + final TestEntityCreate createAnother = new TestEntityCreate(TestVdmEntity.builder().integerValue(1447).build()); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + final TestVdmEntityBatch request = + new TestVdmEntityBatch("") + .addReadOperations(read) + .addChangeSet(create) + .addReadOperations(readByKey) + .addChangeSet(update, delete, createAnother); + final BatchResponse batchResponse = request.executeRequest(destination); + final List result = batchResponse.getReadResult(read); + assertThat(result).isNotEmpty(); + + final TestVdmEntity readByKeyResult = batchResponse.getReadResult(readByKey); + assertThat(readByKeyResult).extracting(TestVdmEntity::getIntegerValue).isEqualTo(9000); + + assertThat(batchResponse.get(0).isSuccess()).isTrue(); + assertThat(batchResponse.get(0).get().getCreatedEntities()).hasSize(1); + assertThat(batchResponse.get(1).isSuccess()).isTrue(); + assertThat(batchResponse.get(1).get().getCreatedEntities()).hasSize(1); + } + } + + @Test + void testNoConnectionTimeoutWhenBatchResponseContainsError() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITH_ERROR))); + + final TestEntityRead read = new TestEntityRead(); + final TestEntityByKey readByKey = new TestEntityByKey(ImmutableMap.of("IntegerValue", 9000)); + final TestVdmEntity deleteEntity = TestVdmEntity.builder().integerValue(14).build(); + final TestEntityDelete delete = new TestEntityDelete(deleteEntity); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + final TestVdmEntityBatch request = + new TestVdmEntityBatch("").addReadOperations(read).addReadOperations(readByKey).addChangeSet(delete); + final BatchResponse batchResponse = request.executeRequest(destination); + final List result = batchResponse.getReadResult(read); + assertThat(result).isNotEmpty(); + final TestVdmEntity readByKeyResult = batchResponse.getReadResult(readByKey); + assertThat(readByKeyResult).extracting(TestVdmEntity::getIntegerValue).isEqualTo(9000); + assertThat(batchResponse.get(0).isSuccess()).isFalse(); + assertThat(batchResponse.get(0).getCause()).isInstanceOfAny(ODataServiceErrorException.class); + } + } + + @Test + @Timeout( value = 300_000L, unit = TimeUnit.MILLISECONDS ) + @Disabled( "Test triggers a ConnectionRequestTimeoutException. Use it only to manually verify behaviour." ) + void testConnectionTimeoutWhenBatchResponseIsNotConsumedFully() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITH_CHANGESET))); + + final TestEntityRead read = new TestEntityRead(); + final TestEntityByKey readByKey = new TestEntityByKey(ImmutableMap.of("IntegerValue", 9000)); + final TestEntityCreate create = new TestEntityCreate(TestVdmEntity.builder().integerValue(1337).build()); + final TestVdmEntity updateEntity = TestVdmEntity.builder().integerValue(13).build(); + final TestEntityUpdate update = new TestEntityUpdate(updateEntity); + final TestVdmEntity deleteEntity = TestVdmEntity.builder().integerValue(14).build(); + final TestEntityDelete delete = new TestEntityDelete(deleteEntity); + final TestEntityCreate createAnother = new TestEntityCreate(TestVdmEntity.builder().integerValue(1447).build()); + final TestVdmEntityBatch request = + new TestVdmEntityBatch("") + .addReadOperations(read) + .addChangeSet(create) + .addReadOperations(readByKey) + .addChangeSet(update, delete, createAnother); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS; i++ ) { + assertThatNoException().isThrownBy(() -> request.executeRequest(destination)); + } + + assertThatThrownBy(() -> request.executeRequest(destination)) + .isInstanceOf(ODataConnectionException.class) + .hasRootCauseExactlyInstanceOf(ConnectionRequestTimeoutException.class) + .hasMessageContaining( + "Please execute your request with try-with-resources to ensure resources are properly closed."); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.java new file mode 100644 index 0000000000..a001031e9b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.java @@ -0,0 +1,371 @@ +package com.sap.cloud.sdk.datamodel.odata.helper.batch; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.helper.CollectionValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.EntitySelectable; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperByKey; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperCreate; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperDelete; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperRead; +import com.sap.cloud.sdk.datamodel.odata.helper.FluentHelperUpdate; +import com.sap.cloud.sdk.datamodel.odata.helper.SingleValuedFluentHelperFunction; +import com.sap.cloud.sdk.datamodel.odata.helper.TestVdmEntity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class TestVdmEntityBatch extends BatchFluentHelperBasic +{ + @Getter + private final String servicePathForBatchRequest; + + { + final AtomicInteger uuidCounter = new AtomicInteger(); + uuidProvider = () -> new UUID(0, uuidCounter.incrementAndGet()); + } + + @Nonnull + @Override + protected TestVdmEntityBatch getThis() + { + return this; + } + + @Nonnull + @Override + public TestVdmEntityChangeset beginChangeSet() + { + return new TestVdmEntityChangeset(this); + } + + static class TestVdmEntityChangeset + extends + BatchChangeSetFluentHelperBasic + { + public TestVdmEntityChangeset( final TestVdmEntityBatch batch ) + { + super(batch, batch); + } + + @Nonnull + @Override + protected TestVdmEntityChangeset getThis() + { + return this; + } + + TestVdmEntityChangeset create( final TestVdmEntity obj ) + { + return addRequestCreate(TestEntityCreate::new, obj); + } + + TestVdmEntityChangeset update( final TestVdmEntity obj ) + { + return addRequestUpdate(TestEntityUpdate::new, obj); + } + + TestVdmEntityChangeset delete( final TestVdmEntity obj ) + { + return addRequestDelete(TestEntityDelete::new, obj); + } + } + + static class TestEntityByKey + extends + FluentHelperByKey> + { + @Getter + private final Map key; + + @SuppressWarnings( "deprecation" ) + public TestEntityByKey( final Map key ) + { + super("", TestVdmEntity.builder().build().getEntityCollection()); + this.key = key; + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return TestVdmEntity.class; + } + } + + static class TestEntityRead extends FluentHelperRead + { + @SuppressWarnings( "deprecation" ) + public TestEntityRead() + { + super("", TestVdmEntity.builder().build().getEntityCollection()); + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return TestVdmEntity.class; + } + } + + static class TestEntityDelete extends FluentHelperDelete + { + @Getter + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + TestEntityDelete( final TestVdmEntity entity ) + { + super("", entity.getEntityCollection()); + this.entity = entity; + } + } + + static class TestEntityUpdate extends FluentHelperUpdate + { + @Getter + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + TestEntityUpdate( final TestVdmEntity entity ) + { + super("", entity.getEntityCollection()); + this.entity = entity; + } + } + + static class TestEntityCreate extends FluentHelperCreate + { + @Getter + private final TestVdmEntity entity; + + @SuppressWarnings( "deprecation" ) + TestEntityCreate( final TestVdmEntity entity ) + { + super("", entity.getEntityCollection()); + this.entity = entity; + } + } + + static class TestFunctionImportSingleResultHttpGet + extends + SingleValuedFluentHelperFunction + { + private final Map values = new HashMap<>(); + + @Nonnull + @Override + protected Class getEntityClass() + { + return String.class; + } + + TestFunctionImportSingleResultHttpGet( @Nonnull final String firstName, @Nonnull final String lastName ) + { + super(""); + values.put("FirstName", firstName); + values.put("LastName", lastName); + } + + @Nullable + @Override + public String executeRequest( @Nonnull final Destination destination ) + { + return "awesomeStuff"; + } + + @Nonnull + @Override + protected Map getParameters() + { + return values; + } + + @Nonnull + @Override + protected String getFunctionName() + { + return "awesomeFunction"; + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return new HttpGet(uri); + } + } + + static class TestFunctionImportCollectionResultHttpGet + extends + CollectionValuedFluentHelperFunction> + { + private final Map values = new HashMap<>(); + + @Nonnull + @Override + protected Class getEntityClass() + { + return String.class; + } + + TestFunctionImportCollectionResultHttpGet() + { + super(""); + } + + @Nullable + @Override + public List executeRequest( @Nonnull final Destination destination ) + { + return Arrays.asList("foo", "bar"); + } + + @Nonnull + @Override + protected Map getParameters() + { + return values; + } + + @Nonnull + @Override + protected String getFunctionName() + { + return "awesomeFunction"; + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return new HttpGet(uri); + } + } + + static class TestFunctionImportSingleEntityResultHttpGet + extends + SingleValuedFluentHelperFunction + { + private final Map values = new HashMap<>(); + + @Nonnull + @Override + protected Class getEntityClass() + { + return TestVdmEntity.class; + } + + TestFunctionImportSingleEntityResultHttpGet( + @Nonnull final int integerValue, + @Nonnull final String stringValue ) + { + super(""); + values.put("IntegerValue", integerValue); + values.put("StringValue", stringValue); + } + + @Nullable + @Override + public TestVdmEntity executeRequest( @Nonnull final Destination destination ) + { + return TestVdmEntity + .builder() + .integerValue((int) values.get("IntegerValue")) + .stringValue((String) values.get("StringValue")) + .build(); + } + + @Nonnull + @Override + protected Map getParameters() + { + return values; + } + + @Nonnull + @Override + protected String getFunctionName() + { + return "awesomeFunction"; + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return new HttpGet(uri); + } + } + + static class TestFunctionImportHttpPost + extends + FluentHelperFunction + { + private final Map values = new HashMap<>(); + + TestFunctionImportHttpPost( @Nonnull final String firstName, @Nonnull final String lastName ) + { + super(""); + values.put("FirstName", firstName); + values.put("LastName", lastName); + } + + @Nonnull + @Override + protected Class getEntityClass() + { + return TestVdmEntity.class; + } + + @Nullable + @Override + public String executeRequest( @Nonnull final Destination destination ) + { + return "awesomeStuff"; + } + + @Nonnull + @Override + protected Map getParameters() + { + return values; + } + + @Nonnull + @Override + protected String getFunctionName() + { + return "awesomeFunction"; + } + + @Nonnull + @Override + protected HttpUriRequest createRequest( @Nonnull final URI uri ) + { + return new HttpPost(uri); + } + } + + interface TestVdmEntitySelectable extends EntitySelectable + { + + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.java new file mode 100644 index 0000000000..ba0d9447ee --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.java @@ -0,0 +1,60 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.time.Month; + +import org.junit.jupiter.api.Test; + +import com.google.gson.TypeAdapter; + +import lombok.SneakyThrows; + +class LocalDateTimeAdapterTest +{ + @Test + @SneakyThrows + void standardFormatCases() + { + final TypeAdapter adapter = new LocalDateTimeAdapter(); + + assertThat(adapter.fromJson("\"/Date(1649669575192)/\"")).isEqualTo("2022-04-11T09:32:55.192"); + } + + @Test + @SneakyThrows + void wrongFormatReturnsNull() + { + final TypeAdapter adapter = new LocalDateTimeAdapter(); + + assertThat(adapter.fromJson("\"Something that is not a date\"")).isNull(); + } + + @Test + @SneakyThrows + void excessivelyLongNumberReturnsNull() + { + final TypeAdapter adapter = new LocalDateTimeAdapter(); + + assertThat(adapter.fromJson("\"/Date(100000000000000000000000)/\"")).isNull(); + } + + @Test + @SneakyThrows + void nonStringValueReturnsNull() + { + final TypeAdapter adapter = new LocalDateTimeAdapter(); + + assertThat(adapter.fromJson("1234")).isNull(); + } + + @Test + void writeLocalDateTime() + { + final TypeAdapter adapter = new LocalDateTimeAdapter(); + + assertThat(adapter.toJson(LocalDateTime.of(2022, Month.APRIL, 11, 11, 36, 55, 123000000))) + .isEqualTo("\"/Date(1649677015123)/\""); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.java new file mode 100644 index 0000000000..447c5072fe --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.java @@ -0,0 +1,64 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalTime; + +import org.junit.jupiter.api.Test; + +import com.google.gson.TypeAdapter; + +import lombok.SneakyThrows; + +class LocalTimeAdapterTest +{ + @Test + @SneakyThrows + void standardFormatCases() + { + final TypeAdapter adapter = new LocalTimeAdapter(); + + assertThat(adapter.fromJson("\"PT13H20M\"")).isEqualTo("13:20"); + assertThat(adapter.fromJson("\"PT22M\"")).isEqualTo("00:22"); + assertThat(adapter.fromJson("\"PT15H\"")).isEqualTo("15:00"); + assertThat(adapter.fromJson("\"PT54S\"")).isEqualTo("00:00:54"); + assertThat(adapter.fromJson("\"PT54.123S\"")).isEqualTo("00:00:54.123"); + } + + @Test + @SneakyThrows + void adapterIgnoresDateFields() + { + final TypeAdapter adapter = new LocalTimeAdapter(); + + assertThat(adapter.fromJson("\"P11Y22M33DT13H22M12.345S\"")).isEqualTo("13:22:12.345"); + assertThat(adapter.fromJson("\"P11Y22M33DT\"")).isEqualTo("00:00"); + } + + @Test + @SneakyThrows + void adapterReturnsNullOnEmptyData() + { + final TypeAdapter adapter = new LocalTimeAdapter(); + + assertThat(adapter.fromJson("\"PT\"")).isNull(); + } + + @Test + @SneakyThrows + void nonStringValueReturnsNull() + { + final TypeAdapter adapter = new LocalTimeAdapter(); + + assertThat(adapter.fromJson("1234")).isNull(); + } + + @Test + void writeLocalTime() + { + final TypeAdapter adapter = new LocalTimeAdapter(); + + assertThat(adapter.toJson(LocalTime.of(1, 2, 3))).isEqualTo("\"PT1H2M3S\""); + assertThat(adapter.toJson(LocalTime.of(14, 54, 32, 123000000))).isEqualTo("\"PT14H54M32.123S\""); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.java new file mode 100644 index 0000000000..7982c1feb2 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.java @@ -0,0 +1,63 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.junit.jupiter.api.Test; + +import com.google.gson.TypeAdapter; + +import lombok.SneakyThrows; + +class ODataBinaryAdapterTest +{ + @Test + @SneakyThrows + void readIntoByteArray() + { + final String testText = "Hello World"; + final String base64encodedString = + Base64.getEncoder().encodeToString(testText.getBytes(StandardCharsets.UTF_8)); + + final TypeAdapter binaryAdapter = new ODataBinaryAdapter(); + + final byte[] parsedResult = binaryAdapter.fromJson("\"" + base64encodedString + "\""); + + assertThat(parsedResult).isEqualTo(testText.getBytes(StandardCharsets.UTF_8)); + } + + @Test + @SneakyThrows + void readInvalidString() + { + final TypeAdapter binaryAdapter = new ODataBinaryAdapter(); + + assertThat(binaryAdapter.fromJson("\"_-/!§$&/()\"")).isNull(); + } + + @Test + void writeFromByteArray() + { + final String testText = "Hello World"; + final String base64encodedString = + Base64.getEncoder().encodeToString(testText.getBytes(StandardCharsets.UTF_8)); + + final TypeAdapter binaryAdapter = new ODataBinaryAdapter(); + + final String writtenResult = binaryAdapter.toJson(testText.getBytes(StandardCharsets.UTF_8)); + + assertThat(writtenResult).isEqualTo("\"" + base64encodedString + "\""); + } + + @Test + void nonStringValueGetsIgnored() + throws IOException + { + final TypeAdapter binaryAdapter = new ODataBinaryAdapter(); + + assertThat(binaryAdapter.fromJson("123")).isNull(); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.java new file mode 100644 index 0000000000..e8b8476b51 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.java @@ -0,0 +1,216 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.io.StringReader; +import java.time.Instant; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.TimeZone; + +import org.junit.jupiter.api.Test; + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; + +class ODataCustomFieldAdapterTest +{ + private static final Gson GSON = new Gson(); + + @Test + void testReadInteger() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader(String.valueOf(Integer.MAX_VALUE))); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo(Integer.MAX_VALUE); + } + + @Test + void testReadLong() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader(String.valueOf(Long.MAX_VALUE))); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo(Long.MAX_VALUE); + } + + @Test + void testReadDouble() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader(String.valueOf(Double.MAX_VALUE))); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo(Double.MAX_VALUE); + } + + @Test + void testReadBoolean() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader(String.valueOf(true))); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo(true); + } + + @Test + void testReadParsesDateString() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("\"/Date(1643775600000)/\"")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.setTime(Date.from(Instant.parse("2022-02-02T04:20:00Z"))); + + assertThat(sut.read(jsonReader)).isEqualTo(calendar); + } + + @Test + void testReadMalformedDateStringLeadsToNull() + throws IOException + { + // The given timestamp exceeds the allowed maximum while still conforming to the regex used to match Date strings. + // ==> The input is a "malformed" Date string. + // Therefore, parsing fails and the adapter should return null. + final JsonReader jsonReader = new JsonReader(new StringReader("\"/Date(" + Long.MAX_VALUE + "9)/\"")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isNull(); + } + + @Test + void testReadDoesntParseOffsetDateTimeString() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("\"/Date(2022-02-02T04:20:00Z)/\"")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo("/Date(2022-02-02T04:20:00Z)/"); + } + + @Test + void testReadString() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("\"foo\"")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo("foo"); + } + + @Test + void testReadArrayOfPrimitives() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("[\"foo\", \"bar\"]")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isEqualTo(Arrays.asList("foo", "bar")); + } + + @Test + void testReadObjectOfPrimitives() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("{\"foo\": \"bar\", \"baz\": 42}")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + final HashMap expected = new HashMap<>(); + expected.put("foo", "bar"); + expected.put("baz", 42); + + assertThat(sut.read(jsonReader)).isEqualTo(expected); + } + + @Test + void testReadDeferredObjectLeadsToNull() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("{\"__deferred\": \"some value\"}")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + assertThat(sut.read(jsonReader)).isNull(); + } + + @Test + void testReadSkipsMetadata() + throws IOException + { + final JsonReader jsonReader = + new JsonReader(new StringReader("{\"__metadata\": {\"key\": \"value\"}, \"foo\": \"bar\", \"baz\": 42}")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + final HashMap expected = new HashMap<>(); + expected.put("foo", "bar"); + expected.put("baz", 42); + + assertThat(sut.read(jsonReader)).isEqualTo(expected); + } + + @Test + void testReadReturnsResultsOnRootLevel() + throws IOException + { + final JsonReader jsonReader = new JsonReader(new StringReader("{\"results\": {\"key\": \"value\"}}")); + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + final HashMap expected = new HashMap<>(); + expected.put("key", "value"); + + assertThat(sut.read(jsonReader)).isEqualTo(expected); + } + + @Test + void testReadExpectsDeferredToBeTheLastObject() + { + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + { + // working example + final JsonReader jsonReader = + new JsonReader(new StringReader("{\"key\": \"value\", \"__deferred\": {\"foo\": \"bar\"}}")); + assertThatNoException().isThrownBy(() -> sut.read(jsonReader)); + } + + { + // failing example + final JsonReader jsonReader = + new JsonReader(new StringReader("{\"__deferred\": {\"foo\": \"bar\"}, \"key\": \"value\"}")); + assertThatThrownBy(() -> sut.read(jsonReader)) + .isExactlyInstanceOf(IllegalStateException.class) + .hasMessageContaining("Expected END_OBJECT but was NAME"); + } + } + + @Test + void testReadExpectsResultsToBeTheLastObject() + { + final ODataCustomFieldAdapter sut = new ODataCustomFieldAdapter(GSON); + + { + // working example + final JsonReader jsonReader = + new JsonReader(new StringReader("{\"key\": \"value\", \"results\": {\"foo\": \"bar\"}}")); + assertThatNoException().isThrownBy(() -> sut.read(jsonReader)); + } + + { + // failing example + final JsonReader jsonReader = + new JsonReader(new StringReader("{\"results\": {\"foo\": \"bar\"}, \"key\": \"value\"}")); + assertThatThrownBy(() -> sut.read(jsonReader)) + .isExactlyInstanceOf(IllegalStateException.class) + .hasMessageContaining("Expected END_OBJECT but was NAME"); + } + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.java new file mode 100644 index 0000000000..9b6486582b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.java @@ -0,0 +1,161 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.jupiter.api.Test; + +class ODataDateTimeStringCalendarConverterTest +{ + @Test + void testDateZeroToCalendar() + { + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final Calendar calendar = sut.toDomainNonNull("/Date(0)/").orNull(); + assertThat(calendar).isNotNull(); + assertThat(calendar.getTimeInMillis()).isEqualTo(0); + assertThat(calendar.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT")); + assertThat(calendar.toInstant()).isEqualTo(Instant.parse("1970-01-01T00:00:00Z")); + } + + @Test + void testDatePositiveToCalendar() + { + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final Calendar calendar = sut.toDomainNonNull("/Date(1643775600000)/").orNull(); + assertThat(calendar).isNotNull(); + assertThat(calendar.getTimeInMillis()).isEqualTo(1643775600000L); + assertThat(calendar.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT")); + assertThat(calendar.toInstant()).isEqualTo(Instant.parse("2022-02-02T04:20:00Z")); + } + + @Test + void testDateNegativeToCalendar() + { + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final Calendar calendar = sut.toDomainNonNull("/Date(-5480538000)/").orNull(); + assertThat(calendar).isNotNull(); + assertThat(calendar.getTimeInMillis()).isEqualTo(-5480538000L); + assertThat(calendar.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT")); + assertThat(calendar.toInstant()).isEqualTo(Instant.parse("1969-10-29T13:37:42Z")); + } + + @Test + void testDateLimitsToCalendar() + { + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + // max + assertThat(sut.toDomainNonNull("/Date(" + Long.MAX_VALUE + ")/").isConvertible()).isTrue(); + // > max + assertThat(sut.toDomainNonNull("/Date(" + Long.MAX_VALUE + "9)/").isConvertible()).isFalse(); + // min + assertThat(sut.toDomainNonNull("/Date(" + Long.MIN_VALUE + ")/").isConvertible()).isTrue(); + // < min + assertThat(sut.toDomainNonNull("/Date(" + Long.MIN_VALUE + "9)/").isConvertible()).isFalse(); + } + + @Test + void testInvalidStringCannotBeConverted() + { + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + assertThat(sut.toDomainNonNull("").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("Not a number").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("0").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("-1").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("1").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("Date()").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("/Date()/").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("/date(0)/").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("/Date(-)/").isNotConvertible()).isTrue(); + assertThat(sut.toDomainNonNull("/Date(+0)/").isNotConvertible()).isTrue(); + } + + @Test + void testCalendarZeroToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.setTime(Date.from(Instant.parse("1970-01-01T00:00:00Z"))); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(0)/"); + } + + @Test + void testCalendarZeroWithOffsetToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+01:00")); + calendar.setTime(Date.from(OffsetDateTime.parse("1970-01-01T00:00:00+01:00").toInstant())); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(-3600000)/"); + } + + @Test + void testCalendarPositiveToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.setTime(Date.from(Instant.parse("2022-02-02T04:20:00Z"))); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(1643775600000)/"); + } + + @Test + void testCalendarPositiveWithOffsetToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT-00:30")); + calendar.setTime(Date.from(OffsetDateTime.parse("2022-02-02T04:20:00-00:30").toInstant())); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(1643777400000)/"); + } + + @Test + void testCalendarNegativeToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.setTime(Date.from(Instant.parse("1969-10-29T13:37:42Z"))); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(-5480538000)/"); + } + + @Test + void testCalendarNegativeWithOffsetToString() + { + final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+01:45")); + calendar.setTime(Date.from(OffsetDateTime.parse("1969-10-29T13:37:42+01:45").toInstant())); + + final ODataDateTimeStringCalendarConverter sut = new ODataDateTimeStringCalendarConverter(); + + final String dateString = sut.fromDomainNonNull(calendar).orNull(); + assertThat(dateString).isNotNull(); + assertThat(dateString).isEqualTo("/Date(-5486838000)/"); + } + +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.java new file mode 100644 index 0000000000..dc2eafea11 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.java @@ -0,0 +1,260 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.datamodel.odata.helper.VdmEntity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +class ODataPrimitiveAdapterTest +{ + private static final String primitivesEntityInput = """ + { + "EdmBinaryProperty" : "Rk9PIEJBUg==", + "EdmBooleanProperty" : true, + "EdmByteProperty" : 200, + "EdmDateTimeProperty" : "/Date(346833000)/", + "EdmDateTimeOffsetProperty" : "/Date(346833000-300)/", + "EdmDecimalProperty" : 9.223372036854776E18, + "EdmDoubleProperty" : 3.1415926535897E93, + "EdmGuidProperty" : "00000000-1111-2222-3333-444444444444", + "EdmInt16Property" : 1980, + "EdmInt32Property" : 16777216, + "EdmInt64Property" : 9223372036854775800, + "EdmSByteProperty" : -120, + "EdmSingleProperty" : 3.14, + "EdmStringProperty" : "TEST STRING", + "EdmTimeProperty" : "PT07H30M00S" + } + """; + + private static final String expectedSerializedEntity = """ + {\ + "versionIdentifier":null,\ + "EdmBinaryProperty":"Rk9PIEJBUg==",\ + "EdmBooleanProperty":true,\ + "EdmByteProperty":200,\ + "EdmDateTimeProperty":"/Date(1649342635567)/",\ + "EdmDateTimeOffsetProperty":"/Date(1649342635567-0300)/",\ + "EdmDecimalProperty":9.223372036854776E+18,\ + "EdmDoubleProperty":3.1415926535897E93,\ + "EdmGuidProperty":"00000000-1111-2222-3333-444444444444",\ + "EdmInt16Property":1980,\ + "EdmInt32Property":16777216,\ + "EdmInt64Property":9223372036854775800,\ + "EdmSByteProperty":-120,\ + "EdmSingleProperty":3.14,\ + "EdmStringProperty":"TEST STRING",\ + "EdmTimeProperty":"PT14H25M34.567S"\ + }\ + """; + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataVdmEntityAdapterFactory.class ) + public static class PrimitivesEntity extends VdmEntity + { + @SerializedName( "EdmBinaryProperty" ) + @JsonProperty( "EdmBinaryProperty" ) + @Nullable + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataBinaryAdapter.class ) + private byte[] edmBinaryProperty; + + @SerializedName( "EdmBooleanProperty" ) + @JsonProperty( "EdmBooleanProperty" ) + @Nullable + @JsonAdapter( com.sap.cloud.sdk.s4hana.datamodel.odata.adapter.ODataBooleanAdapter.class ) + private Boolean edmBooleanProperty; + + @SerializedName( "EdmByteProperty" ) + @JsonProperty( "EdmByteProperty" ) + @Nullable + private Short edmByteProperty; + + @SerializedName( "EdmDateTimeProperty" ) + @JsonProperty( "EdmDateTimeProperty" ) + @Nullable + @JsonAdapter( LocalDateTimeAdapter.class ) + @JsonSerialize( using = JacksonLocalDateTimeSerializer.class ) + @JsonDeserialize( using = JacksonLocalDateTimeDeserializer.class ) + private LocalDateTime edmDateTimeProperty; + + @SerializedName( "EdmDateTimeOffsetProperty" ) + @JsonProperty( "EdmDateTimeOffsetProperty" ) + @Nullable + @JsonAdapter( ZonedDateTimeAdapter.class ) + @JsonSerialize( using = JacksonZonedDateTimeSerializer.class ) + @JsonDeserialize( using = JacksonZonedDateTimeDeserializer.class ) + private ZonedDateTime edmDateTimeOffsetProperty; + + @SerializedName( "EdmDecimalProperty" ) + @JsonProperty( "EdmDecimalProperty" ) + @Nullable + private BigDecimal edmDecimalProperty; + + @SerializedName( "EdmDoubleProperty" ) + @JsonProperty( "EdmDoubleProperty" ) + @Nullable + private Double edmDoubleProperty; + + @SerializedName( "EdmGuidProperty" ) + @JsonProperty( "EdmGuidProperty" ) + @Nullable + private UUID edmGuidProperty; + + @SerializedName( "EdmInt16Property" ) + @JsonProperty( "EdmInt16Property" ) + @Nullable + private Short edmInt16Property; + + @SerializedName( "EdmInt32Property" ) + @JsonProperty( "EdmInt32Property" ) + @Nullable + private Integer edmInt32Property; + + @SerializedName( "EdmInt64Property" ) + @JsonProperty( "EdmInt64Property" ) + @Nullable + private Long edmInt64Property; + + @SerializedName( "EdmSByteProperty" ) + @JsonProperty( "EdmSByteProperty" ) + @Nullable + private Byte edmSByteProperty; + + @SerializedName( "EdmSingleProperty" ) + @JsonProperty( "EdmSingleProperty" ) + @Nullable + private Float edmSingleProperty; + + @SerializedName( "EdmStringProperty" ) + @JsonProperty( "EdmStringProperty" ) + @Nullable + private String edmStringProperty; + + @SerializedName( "EdmTimeProperty" ) + @JsonProperty( "EdmTimeProperty" ) + @Nullable + @JsonSerialize( using = JacksonLocalTimeSerializer.class ) + @JsonDeserialize( using = JacksonLocalTimeDeserializer.class ) + @JsonAdapter( LocalTimeAdapter.class ) + private LocalTime edmTimeProperty; + + @Getter + @Setter + @Builder.Default + private transient String servicePath = "NOT_APPLICABLE"; + + @Override + protected String getEntityCollection() + { + return "PrimitivesCollection"; + } + + @Nonnull + @Override + public Class getType() + { + return PrimitivesEntity.class; + } + } + + @Test + void testGsonDeserialization() + { + final PrimitivesEntity primitivesEntity = new Gson().fromJson(primitivesEntityInput, PrimitivesEntity.class); + + assertThat(primitivesEntity).isNotNull(); + + assertThat(primitivesEntity.getEdmBinaryProperty()).containsExactly('F', 'O', 'O', ' ', 'B', 'A', 'R'); + assertThat(primitivesEntity.getEdmBooleanProperty()).isTrue(); + assertThat(primitivesEntity.getEdmByteProperty()).isEqualTo((short) 200); + + assertThat(primitivesEntity.getEdmDecimalProperty()).isEqualTo(new BigDecimal("9.223372036854776E18")); + assertThat(primitivesEntity.getEdmDoubleProperty()).isEqualTo(3.1415926535897E+93d); + assertThat(primitivesEntity.getEdmGuidProperty()) + .isEqualTo(UUID.fromString("00000000-1111-2222-3333-444444444444")); + assertThat(primitivesEntity.getEdmInt16Property()).isEqualTo((short) 1980); + assertThat(primitivesEntity.getEdmInt32Property()).isEqualTo(16777216); + assertThat(primitivesEntity.getEdmInt64Property()).isEqualTo(9223372036854775800L); + assertThat(primitivesEntity.getEdmSByteProperty()).isEqualTo((byte) -120); + assertThat(primitivesEntity.getEdmSingleProperty()).isEqualTo(3.14f); + assertThat(primitivesEntity.getEdmStringProperty()).isEqualTo("TEST STRING"); + assertThat(primitivesEntity.getEdmTimeProperty()).isEqualTo("07:30"); + } + + @Test + void testGsonSerialization() + throws Exception + { + final LocalDateTime expectedEdmDateTimeProperty = + LocalDateTime.of(2022, Month.APRIL, 7, 14, 43, 55, 567 * 1000000); + + final ZonedDateTime expectedEdmDateTimeOffsetProperty = + expectedEdmDateTimeProperty.atZone(ZoneId.of("GMT-05:00")); + + final LocalTime expectedEdmTimeProperty = LocalTime.of(14, 25, 34, 567 * 1000000); + + final PrimitivesEntity primitivesEntity = + PrimitivesEntity + .builder() + .edmBinaryProperty(new byte[] { 'F', 'O', 'O', ' ', 'B', 'A', 'R' }) + .edmBooleanProperty(true) + .edmByteProperty((short) 200) + .edmDateTimeProperty(expectedEdmDateTimeProperty) + .edmDateTimeOffsetProperty(expectedEdmDateTimeOffsetProperty) + .edmDecimalProperty(new BigDecimal("9.223372036854776E18")) + .edmDoubleProperty(3.1415926535897E93) + .edmGuidProperty(UUID.fromString("00000000-1111-2222-3333-444444444444")) + .edmInt16Property((short) 1980) + .edmInt32Property(16777216) + .edmInt64Property(9223372036854775800L) + .edmSByteProperty((byte) -120) + .edmSingleProperty(3.14f) + .edmStringProperty("TEST STRING") + .edmTimeProperty(expectedEdmTimeProperty) + .build(); + + // test gson + final GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls().disableHtmlEscaping(); + final String actualGsonSerializedEntity = gsonBuilder.create().toJson(primitivesEntity); + assertThat(actualGsonSerializedEntity).isEqualTo(expectedSerializedEntity); + + // test jackson + final ObjectMapper jacksonMapper = new ObjectMapper(); + final String actualJacksonSerializedEntity = jacksonMapper.writeValueAsString(primitivesEntity); + assertThat(actualJacksonSerializedEntity).isEqualTo(expectedSerializedEntity); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java new file mode 100644 index 0000000000..342577a681 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.java @@ -0,0 +1,90 @@ +package com.sap.cloud.sdk.s4hana.datamodel.odata.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.Test; + +import com.google.gson.TypeAdapter; + +import lombok.SneakyThrows; + +class ZonedDateTimeAdapterTest +{ + @Test + @SneakyThrows + void standardFormatCases() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("\"/Date(1649669575192)/\"")).isEqualTo("2022-04-11T09:32:55.192Z"); + assertThat(adapter.fromJson("\"/Date(1649669575192+0120)/\"")).isEqualTo("2022-04-11T09:32:55.192+02:00"); + } + + @Test + @SneakyThrows + void declaredOffsetTooLargeReturnsNull() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("\"/Date(1649669575192+1441)/\"")).isNull(); + } + + @Test + @SneakyThrows + void invalidPatternReturnsNull() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("\"Something that is not a date\"")).isNull(); + } + + @Test + @SneakyThrows + void isoPatternReturnsNull() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("\"2022-04-11T12:47:14.1234567-02:30\"")).isNull(); + assertThat(adapter.fromJson("\"2022-04-11T12:47:14.1234567+4:30\"")).isNull(); + assertThat(adapter.fromJson("\"2022-04-11T12:47:14.1234567Z\"")).isNull(); + assertThat(adapter.fromJson("\"2022-04-11T12:47:14\"")).isNull(); + assertThat(adapter.fromJson("\"2022-04-11T12:47\"")).isNull(); + assertThat(adapter.fromJson("\"2-4-1T2:7\"")).isNull(); + } + + @Test + @SneakyThrows + void excessivelyLongNumberReturnsNull() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("\"/Date(100000000000000000000000)/\"")).isNull(); + } + + @Test + @SneakyThrows + void nonStringValueReturnsNull() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat(adapter.fromJson("1234")).isNull(); + } + + @Test + void writeZonedDateTime() + { + final TypeAdapter adapter = new ZonedDateTimeAdapter(); + + assertThat( + adapter.toJson(ZonedDateTime.of(2022, Month.APRIL.getValue(), 11, 11, 36, 55, 123000000, ZoneId.of("GMT")))) + .isEqualTo("\"/Date(1649677015123)/\""); + assertThat( + adapter + .toJson(ZonedDateTime.of(2022, Month.APRIL.getValue(), 11, 11, 36, 55, 123000000, ZoneId.of("GMT+2")))) + .isEqualTo("\"/Date(1649677015123+0120)/\""); + } +} diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt new file mode 100644 index 0000000000..32772d9ba7 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt @@ -0,0 +1,10 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt new file mode 100644 index 0000000000..436ae71527 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt @@ -0,0 +1,17 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt new file mode 100644 index 0000000000..7c8d4ab875 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt @@ -0,0 +1,18 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 +Accept: application/json +Content-Type: application/json +foo: bar + +{} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt new file mode 100644 index 0000000000..cb7449970a --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt @@ -0,0 +1,90 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"awesomeFunction":"awesomeStuff"}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"awesomeFunction":["foo","bar"]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"IntegerValue":33,"StringValue":"Alice"}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt new file mode 100644 index 0000000000..7a25853f38 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt @@ -0,0 +1,83 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST Entities HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"IntegerValue":12} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET Entities(9000) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 4 + +PATCH Entities(13) HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{} + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 5 + +DELETE Entities(14) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000003-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 6 + +DELETE Entities(15) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 7 + +DELETE Entities(16) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000004-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt new file mode 100644 index 0000000000..4864bce1eb --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt @@ -0,0 +1,90 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 +Accept: application/json +header-read_all: read_all + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST Entities HTTP/1.1 +Accept: application/json +Content-Type: application/json +header-create: create + +{"IntegerValue":12} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET Entities(9000) HTTP/1.1 +Accept: application/json +header-read_by_key: read_by_key + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 4 + +PATCH Entities(13) HTTP/1.1 +Accept: application/json +Content-Type: application/json +header-update: update + +{} + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 5 + +DELETE Entities(14) HTTP/1.1 +Accept: application/json +header-delete: delete-entity14 + + +--changeset_00000000-0000-0000-0000-000000000003-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 6 + +DELETE Entities(15) HTTP/1.1 +Accept: application/json +header-delete: delete-entity15 + + +--changeset_00000000-0000-0000-0000-000000000004 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 7 + +DELETE Entities(16) HTTP/1.1 +Accept: application/json +header-delete: delete-entity16 + + +--changeset_00000000-0000-0000-0000-000000000004-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt new file mode 100644 index 0000000000..68718fe227 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt @@ -0,0 +1,63 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt new file mode 100644 index 0000000000..040aa489ca --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt @@ -0,0 +1,62 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1337}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 204 No response. +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json + +{"d":{"IntegerValue":1447}} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt new file mode 100644 index 0000000000..7da32711f4 --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt @@ -0,0 +1,29 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt new file mode 100644 index 0000000000..cca2d42f9b --- /dev/null +++ b/datamodel/odata-core-apache-httpclient5/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt @@ -0,0 +1,19 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"d":{"StringValue":"Foo","IntegerValue":9000}} + +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/pom.xml b/datamodel/pom.xml index fb51f9f405..430a4cd4a8 100644 --- a/datamodel/pom.xml +++ b/datamodel/pom.xml @@ -35,6 +35,7 @@ odata-client odata-client-apache-httpclient5 odata + odata-core-apache-httpclient5 odata-v4 soap From 6eae4256a948877116a8a15c056407b3b7b852f9 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Tue, 8 Sep 2026 14:41:10 -0300 Subject: [PATCH 2/9] add odata-v4-core-apache-httpclient5 module with hc5 migration and removed CSRF token control methods --- .../odata-v4-core-apache-httpclient5/pom.xml | 145 ++ .../adapter/CustomBigDecimalTypeAdapter.java | 50 + .../adapter/GsonCustomFieldAdapter.java | 66 + .../adapter/GsonODataConverterAdapter.java | 56 + .../adapter/GsonVdmAdapterFactory.java | 47 + .../odatav4/adapter/GsonVdmEntityAdapter.java | 350 +++++ .../adapter/GsonVdmEntityListAdapter.java | 117 ++ .../odatav4/adapter/GsonVdmEnumAdapter.java | 62 + .../adapter/JacksonVdmEnumDeserializer.java | 83 ++ .../adapter/JacksonVdmEnumSerializer.java | 58 + .../adapter/JacksonVdmObjectDeserializer.java | 242 ++++ .../adapter/JacksonVdmObjectSerializer.java | 176 +++ .../adapter/ODataGenericConverter.java | 102 ++ .../odatav4/core/AbstractBoundOperation.java | 62 + .../AbstractEntityBasedRequestBuilder.java | 52 + .../odatav4/core/AbstractRequestBuilder.java | 167 +++ .../core/AbstractStructuredPropertyQuery.java | 112 ++ .../odatav4/core/ActionRequestBuilder.java | 147 ++ .../core/ActionResponseCollection.java | 91 ++ .../odatav4/core/ActionResponseSingle.java | 93 ++ .../odatav4/core/BatchRequestBuilder.java | 145 ++ .../datamodel/odatav4/core/BatchResponse.java | 268 ++++ .../datamodel/odatav4/core/BoundAction.java | 158 +++ .../datamodel/odatav4/core/BoundFunction.java | 739 ++++++++++ .../odatav4/core/BoundOperation.java | 38 + .../CollectionValueActionRequestBuilder.java | 105 ++ ...CollectionValueFunctionRequestBuilder.java | 107 ++ .../odatav4/core/ComplexProperty.java | 82 ++ .../odatav4/core/ComplexPropertyQuery.java | 50 + .../odatav4/core/CountRequestBuilder.java | 137 ++ .../odatav4/core/CreateRequestBuilder.java | 141 ++ .../odatav4/core/DecimalDescriptor.java | 28 + .../odatav4/core/DeleteRequestBuilder.java | 163 +++ .../odatav4/core/FunctionRequestBuilder.java | 59 + .../odatav4/core/GetAllRequestBuilder.java | 258 ++++ .../odatav4/core/GetByKeyRequestBuilder.java | 156 +++ .../core/ModificationRequestBuilder.java | 16 + .../odatav4/core/ModificationResponse.java | 147 ++ .../core/NavigableEntityCollection.java | 46 + .../odatav4/core/NavigableEntitySingle.java | 153 ++ .../odatav4/core/NavigationProperty.java | 74 + .../core/NavigationPropertyCollection.java | 80 ++ .../NavigationPropertyCollectionQuery.java | 129 ++ .../core/NavigationPropertySingleQuery.java | 60 + .../core/OperationsOnEntityCollections.java | 125 ++ .../sdk/datamodel/odatav4/core/Property.java | 13 + .../odatav4/core/ProtocolQueryFilter.java | 47 + .../odatav4/core/ProtocolQueryRead.java | 28 + .../core/ProtocolQueryReadCollection.java | 47 + .../odatav4/core/ReadRequestBuilder.java | 15 + .../odatav4/core/RequestBuilder.java | 54 + .../core/RequestBuilderExecutable.java | 52 + .../odatav4/core/ResourcePathUtil.java | 24 + .../odatav4/core/SearchExpression.java | 118 ++ .../core/ServiceWithNavigableEntities.java | 215 +++ .../ServiceWithNavigableEntitiesImpl.java | 308 ++++ .../odatav4/core/SimpleProperty.java | 241 ++++ .../core/SingleValueActionRequestBuilder.java | 106 ++ .../SingleValueFunctionRequestBuilder.java | 110 ++ .../odatav4/core/StructuredProperty.java | 17 + .../odatav4/core/UpdateRequestBuilder.java | 288 ++++ .../core/UpdateRequestHelperPatch.java | 211 +++ .../odatav4/core/UpdateRequestHelperPut.java | 27 + .../datamodel/odatav4/core/VdmComplex.java | 12 + .../sdk/datamodel/odatav4/core/VdmEntity.java | 88 ++ .../datamodel/odatav4/core/VdmEntitySet.java | 8 + .../datamodel/odatav4/core/VdmEntityUtil.java | 38 + .../sdk/datamodel/odatav4/core/VdmEnum.java | 60 + .../sdk/datamodel/odatav4/core/VdmObject.java | 403 ++++++ .../odatav4/expression/EntityReference.java | 20 + .../odatav4/expression/FieldOrdering.java | 80 ++ .../odatav4/expression/FilterableBoolean.java | 132 ++ .../expression/FilterableCollection.java | 343 +++++ .../odatav4/expression/FilterableComplex.java | 94 ++ .../odatav4/expression/FilterableDate.java | 192 +++ .../expression/FilterableDateTime.java | 255 ++++ .../expression/FilterableDuration.java | 214 +++ .../odatav4/expression/FilterableEnum.java | 135 ++ .../odatav4/expression/FilterableGuid.java | 14 + .../odatav4/expression/FilterableNumeric.java | 340 +++++ .../expression/FilterableNumericDecimal.java | 249 ++++ .../expression/FilterableNumericInteger.java | 207 +++ .../odatav4/expression/FilterableString.java | 336 +++++ .../odatav4/expression/FilterableTime.java | 96 ++ .../odatav4/expression/FilterableValue.java | 284 ++++ .../sdk/datamodel/odatav4/TestUtility.java | 39 + .../adapter/BigDecimalSerialisationTest.java | 65 + .../adapter/SerializerAdapterTest.java | 79 ++ .../odatav4/core/BoundActionTest.java | 126 ++ .../odatav4/core/BoundFunctionTest.java | 331 +++++ ...llectionValueActionRequestBuilderTest.java | 236 ++++ ...ectionValueFunctionRequestBuilderTest.java | 182 +++ .../core/CustomFilterExpressionTest.java | 67 + .../odatav4/core/FieldSerializationTest.java | 239 ++++ .../odatav4/core/HeadersHandlingTest.java | 135 ++ .../core/HttpResponseEvaluationTest.java | 204 +++ .../core/ModificationResponseTest.java | 164 +++ .../odatav4/core/NestedEntityTest.java | 413 ++++++ .../odatav4/core/ODataRequestImplTest.java | 54 + .../core/ODataV4BatchRequestUnitTest.java | 300 ++++ .../core/ODatav4BatchConnectionTest.java | 177 +++ .../core/RequestBuilderEtagParsingTest.java | 371 +++++ .../SingleValueActionRequestBuilderTest.java | 316 +++++ ...SingleValueFunctionRequestBuilderTest.java | 192 +++ .../datamodel/odatav4/core/TestEntity.java | 86 ++ .../odatav4/core/TestEntityService.java | 108 ++ .../core/UpdateRequestHelperPatchTest.java | 633 +++++++++ .../core/UpdateRequestHelperPutTest.java | 159 +++ .../datamodel/odatav4/core/VdmEntityTest.java | 69 + .../expression/FilterableOperandsTest.java | 575 ++++++++ .../referenceservice/BooleanQueriesTest.java | 46 + .../CollectionQueriesTest.java | 51 + .../ComparisonQueriesTest.java | 112 ++ .../referenceservice/CountEntityUnitTest.java | 110 ++ .../odatav4/referenceservice/ETagTest.java | 375 +++++ .../EncodedParameterTest.java | 21 + .../referenceservice/EncodedQueryTest.java | 139 ++ ...tBatchReferenceServiceIntegrationTest.java | 130 ++ ...ntBatchResponseParsingIntegrationTest.java | 142 ++ ...ataClientBatchResponseParsingUnitTest.java | 399 ++++++ .../ODataReferenceServiceByKeyTest.java | 96 ++ .../ODataReferenceServiceTest.java | 132 ++ .../ODataV4BatchReferenceServiceUnitTest.java | 241 ++++ .../PropertyCustomSerializationTest.java | 175 +++ .../PropertySerializationTest.java | 122 ++ .../odatav4/referenceservice/README.md | 13 + .../referenceservice/SearchQueriesTest.java | 81 ++ .../SelectNestedComplexPropertiesTest.java | 180 +++ .../referenceservice/TripPinUtility.java | 47 + .../referenceservice/UnboundActionTest.java | 39 + .../referenceservice/UnboundFunctionTest.java | 47 + .../namespaces/trippin/Airline.java | 178 +++ .../namespaces/trippin/Airport.java | 263 ++++ .../namespaces/trippin/AirportLocation.java | 175 +++ .../namespaces/trippin/Beverage.java | 158 +++ .../namespaces/trippin/City.java | 243 ++++ .../namespaces/trippin/Feature.java | 72 + .../namespaces/trippin/Location.java | 173 +++ .../namespaces/trippin/Person.java | 1246 +++++++++++++++++ .../namespaces/trippin/PersonGender.java | 66 + .../namespaces/trippin/PlanItem.java | 286 ++++ .../namespaces/trippin/Trip.java | 560 ++++++++ .../services/DefaultTrippinService.java | 242 ++++ .../services/TrippinService.java | 398 ++++++ .../ActionNoParametersRequestBody.json | 1 + .../CsrfTokenHandlingTest/CreateResponse.json | 4 + .../ETagTest/GetAllResponseBody.json | 20 + .../ETagTest/GetSingleResponseBody.json | 5 + .../HeadersHandlingTest/CreateResponse.json | 4 + .../BatchEmptyRequest.txt | 1 + .../BatchEmptyResponse.txt | 2 + .../BatchOnlyReadsErrorResponse.txt | 21 + .../BatchOnlyReadsMissingRequest.txt | 28 + .../BatchOnlyReadsMissingResponse.txt | 20 + .../BatchOnlyReadsRequest.txt | 19 + .../BatchOnlyReadsResponse.txt | 21 + .../BatchReadsAndWritesErrorRequest.txt | 37 + .../BatchReadsAndWritesErrorResponse.txt | 23 + ...AndWritesErrorResponseWithoutChangeset.txt | 19 + .../BatchReadsAndWritesSuccessRequest.txt | 37 + .../BatchReadsAndWritesSuccessResponse.txt | 37 + .../BatchEmptyRequest.txt | 1 + .../BatchEmptyResponse.txt | 2 + .../BatchOnlyReadsErrorResponse.txt | 20 + .../BatchOnlyReadsRequest.txt | 19 + .../BatchOnlyReadsResponse.txt | 21 + .../BatchReadsAndWritesSuccessRequest.txt | 37 + .../BatchReadsAndWritesSuccessResponse.txt | 37 + .../BatchReadsAndWritesSuccessRequest.txt | 100 ++ .../BatchReadsAndWritesSuccessResponse.txt | 87 ++ .../BatchResponseWithChangeset.txt | 51 + .../BatchResponseWithError.txt | 28 + .../BatchResponseWithoutChangeset.txt | 19 + .../ActionComplexTypeRequestBody.json | 6 + .../ActionEntityRequestBody.json | 4 + .../ActionNoParametersRequestBody.json | 1 + .../ActionNullEntityRequestBody.json | 3 + .../ActionParametersRequestBody.json | 9 + .../src/test/resources/unused_trippin.edmx | 219 +++ 179 files changed, 24304 insertions(+) create mode 100644 datamodel/odata-v4-core-apache-httpclient5/pom.xml create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/CustomBigDecimalTypeAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonCustomFieldAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonODataConverterAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmAdapterFactory.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityListAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEnumAdapter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumDeserializer.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumSerializer.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectDeserializer.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectSerializer.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractBoundOperation.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractEntityBasedRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractStructuredPropertyQuery.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseCollection.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseSingle.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchResponse.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundAction.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunction.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundOperation.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexProperty.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexPropertyQuery.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CountRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DecimalDescriptor.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponse.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntityCollection.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntitySingle.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationProperty.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollection.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollectionQuery.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertySingleQuery.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/OperationsOnEntityCollections.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/Property.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryFilter.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryRead.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryReadCollection.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderExecutable.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ResourcePathUtil.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SearchExpression.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntities.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntitiesImpl.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SimpleProperty.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/StructuredProperty.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatch.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPut.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmComplex.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntity.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntitySet.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityUtil.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEnum.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmObject.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/EntityReference.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FieldOrdering.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableBoolean.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableCollection.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableComplex.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDate.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDateTime.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDuration.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableEnum.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableGuid.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumeric.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericDecimal.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericInteger.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableString.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableTime.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableValue.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/TestUtility.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/BigDecimalSerialisationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/SerializerAdapterTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundActionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunctionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilderTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilderTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CustomFilterExpressionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HeadersHandlingTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HttpResponseEvaluationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponseTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/NestedEntityTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataRequestImplTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataV4BatchRequestUnitTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODatav4BatchConnectionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderEtagParsingTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilderTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilderTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntity.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntityService.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatchTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPutTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableOperandsTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/BooleanQueriesTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CollectionQueriesTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ComparisonQueriesTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CountEntityUnitTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ETagTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedParameterTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedQueryTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchReferenceServiceIntegrationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingIntegrationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingUnitTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceByKeyTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataV4BatchReferenceServiceUnitTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertyCustomSerializationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertySerializationTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/README.md create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SearchQueriesTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SelectNestedComplexPropertiesTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/TripPinUtility.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundActionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundFunctionTest.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airline.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airport.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/AirportLocation.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Beverage.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/City.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Feature.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Location.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Person.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PersonGender.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PlanItem.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Trip.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/DefaultTrippinService.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/TrippinService.java create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CollectionValueActionRequestBuilderTest/ActionNoParametersRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CsrfTokenHandlingTest/CreateResponse.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetAllResponseBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetSingleResponseBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/HeadersHandlingTest/CreateResponse.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsErrorResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponseWithoutChangeset.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsErrorResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessRequest.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessResponse.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithChangeset.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithError.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithoutChangeset.txt create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionComplexTypeRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionEntityRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNoParametersRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNullEntityRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionParametersRequestBody.json create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/resources/unused_trippin.edmx diff --git a/datamodel/odata-v4-core-apache-httpclient5/pom.xml b/datamodel/odata-v4-core-apache-httpclient5/pom.xml new file mode 100644 index 0000000000..cb906a52f8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/pom.xml @@ -0,0 +1,145 @@ + + + 4.0.0 + + com.sap.cloud.sdk.datamodel + datamodel-parent + 5.35.0-SNAPSHOT + + odata-v4-core-apache-httpclient5 + jar + Data Model - OData V4 Services - Core (HttpClient 5) + OData V4 Services data model (VDM) - core classes using Apache HttpClient 5. + https://sap.github.io/cloud-sdk/docs/java/getting-started + + SAP SE + https://www.sap.com + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + SAP + cloudsdk@sap.com + SAP SE + https://www.sap.com + + + + false + true + + + + com.sap.cloud.sdk.datamodel + odata-client-apache-httpclient5 + ${project.version} + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-core + + + com.sap.cloud.sdk.cloudplatform + cloudplatform-connectivity + + + com.sap.cloud.sdk.cloudplatform + connectivity-apache-httpclient5 + + + com.sap.cloud.sdk.datamodel + fluent-result + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + com.google.code.gson + gson + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.client5 + httpclient5 + + + io.vavr + vavr + + + + org.projectlombok + lombok + provided + + + + com.sap.cloud.sdk + testutil + test + + + org.assertj + assertj-core + test + + + org.skyscreamer + jsonassert + test + + + org.mockito + mockito-core + test + + + org.wiremock + wiremock + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + commons-beanutils:commons-beanutils + + + + + + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/CustomBigDecimalTypeAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/CustomBigDecimalTypeAdapter.java new file mode 100644 index 0000000000..eeb7f604b8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/CustomBigDecimalTypeAdapter.java @@ -0,0 +1,50 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.math.BigDecimal; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * Custom type adapter for BigDecimal that doesn't serialise BigDecimal into it's scientific notation. For e.g. if the + * value of the attribute is 0.000000002, using this serializer ensures that it doesn't get converted to 2E-9. + */ +class CustomBigDecimalTypeAdapter extends TypeAdapter +{ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final BigDecimal value ) + throws IOException + { + if( value == null ) { + out.nullValue(); + } else { + final Number number = new LazilyParsedNumber(value.toPlainString()); + out.value(number); + } + } + + @Override + @Nullable + public BigDecimal read( @Nonnull final JsonReader in ) + throws IOException + { + if( in.peek() == JsonToken.NULL ) { + in.nextNull(); + return null; + } + try { + return new BigDecimal(in.nextString()); + } + catch( final NumberFormatException e ) { + throw new JsonSyntaxException("The string could not be parsed as decimal.", e); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonCustomFieldAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonCustomFieldAdapter.java new file mode 100644 index 0000000000..3036b955db --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonCustomFieldAdapter.java @@ -0,0 +1,66 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import lombok.RequiredArgsConstructor; + +/** + * For internal use only by data model classes. + */ +@RequiredArgsConstructor +public class GsonCustomFieldAdapter extends TypeAdapter +{ + private static final Type MAP_TYPE = new TypeToken>() + { + }.getType(); + + @Nonnull + private final Gson gson; + + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final Object value ) + { + // TODO missing implementation for array. + if( value instanceof String ) { + gson.toJson(new JsonPrimitive((String) value), out); + } + if( value instanceof Number ) { + gson.toJson(new JsonPrimitive((Number) value), out); + } + if( value instanceof Boolean ) { + gson.toJson(new JsonPrimitive((Boolean) value), out); + } + gson.toJson(value, MAP_TYPE, out); + } + + @Override + @Nullable + public Object read( @Nonnull final JsonReader in ) + throws IOException + { + // TODO missing implementation for array. + if( JsonToken.STRING == in.peek() ) { + return in.nextString(); + } + if( JsonToken.NUMBER == in.peek() ) { + return in.nextDouble(); + } + if( JsonToken.BOOLEAN == in.peek() ) { + return in.nextBoolean(); + } + return gson.fromJson(in, MAP_TYPE); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonODataConverterAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonODataConverterAdapter.java new file mode 100644 index 0000000000..71344b6657 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonODataConverterAdapter.java @@ -0,0 +1,56 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor( staticName = "of" ) +class GsonODataConverterAdapter extends TypeAdapter +{ + private final AbstractTypeConverter converter; + + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final JavaT value ) + throws IOException + { + if( value == null ) { + out.nullValue(); + } else { + final ConvertedObject jsonValue = converter.toDomain(value); + if( jsonValue.isNotConvertible() ) { + log.warn("Not serializable: {}", value); + } else { + out.value(jsonValue.get()); + } + } + } + + @Override + @Nullable + public JavaT read( @Nonnull final JsonReader in ) + throws IOException + { + if( in.peek() == JsonToken.NULL ) { + in.nextNull(); + return null; + } + final String value = in.nextString(); + final ConvertedObject convertedObject = converter.fromDomain(value); + if( convertedObject.isNotConvertible() ) { + log.warn("Not deserializable: {}", value); + } + return convertedObject.orNull(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmAdapterFactory.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmAdapterFactory.java new file mode 100644 index 0000000000..cc6bd7ef54 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmAdapterFactory.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.math.BigDecimal; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmObject; + +import lombok.extern.slf4j.Slf4j; + +/** + * General purpose VDM adapter factory. + */ +@Slf4j +public class GsonVdmAdapterFactory implements TypeAdapterFactory +{ + @SuppressWarnings( "unchecked" ) + @Override + @Nullable + public TypeAdapter create( @Nonnull final Gson gson, @Nonnull final TypeToken type ) + { + final Class rawType = type.getRawType(); + if( VdmEnum.class.isAssignableFrom(rawType) ) { + return (TypeAdapter) new GsonVdmEnumAdapter<>((Class) rawType); + } + if( VdmObject.class.isAssignableFrom(rawType) ) { + return (TypeAdapter) new GsonVdmEntityAdapter<>(this, gson, rawType); + } + if( BigDecimal.class.isAssignableFrom(rawType) ) { + return (TypeAdapter) new CustomBigDecimalTypeAdapter(); + } + + for( final ODataGenericConverter converter : ODataGenericConverter.DEFAULT_CONVERTERS ) { + if( converter.getType().isAssignableFrom(rawType) ) { + return (TypeAdapter) GsonODataConverterAdapter.of(converter); + } + } + log.trace("Could not find custom type adapter for type {}.", rawType); + return null; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityAdapter.java new file mode 100644 index 0000000000..e256f1bbf3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityAdapter.java @@ -0,0 +1,350 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmObject; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * For internal use only by data model classes + * + * @param + * The entity type. + */ +@Slf4j +public class GsonVdmEntityAdapter extends TypeAdapter> +{ + @Nonnull + private final Gson gson; + @Nonnull + private final Class entityRawType; + @Nonnull + private final TypeAdapterFactory adapterFactory; + + @Nullable + private TypeAdapter> delegateAdapter = null; + @Nullable + private GsonVdmEntityAdapter superClassAdapter = null; + + @Nonnull + private final Map entityProperties; + @Nonnull + private final TypeAdapter customFieldAdapter; + + @AllArgsConstructor + private static class PropertySerializationInfo + { + @Getter + private final Field javaField; + @Getter + private final TypeAdapter fieldAdapter; + } + + private TypeAdapter getAdapterFromField( final Field entityField, final Gson gson ) + { + if( entityField.isAnnotationPresent(JsonAdapter.class) ) { + try { + return (TypeAdapter) entityField + .getAnnotation(JsonAdapter.class) + .value() + .getDeclaredConstructor() + .newInstance(); + } + catch( final + InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e ) { + log.warn("Could not instantiate the field '" + entityField.getName() + "'.", e); + } + } + if( Iterable.class.isAssignableFrom(entityField.getType()) ) { + final ParameterizedType entityFieldTypeParams = (ParameterizedType) entityField.getGenericType(); + final Type listEntityType = entityFieldTypeParams.getActualTypeArguments()[0]; + final TypeAdapter innerTypeAdapter = gson.getAdapter(TypeToken.get(listEntityType)); + return new GsonVdmEntityListAdapter<>(gson, innerTypeAdapter); + } + final TypeAdapter fieldAdapter = adapterFactory.create(gson, TypeToken.get(entityField.getType())); + if( fieldAdapter != null ) { + return fieldAdapter; + } + + return gson.getAdapter(entityField.getType()); + } + + /** + * For internal use only by data model classes. + * + * @param adapterFactory + * The adapter type factory. + * @param gson + * The GSON instance. + * @param entityRawType + * The entity type reference. + */ + @SuppressWarnings( "unchecked" ) + public GsonVdmEntityAdapter( + @Nonnull final TypeAdapterFactory adapterFactory, + @Nonnull final Gson gson, + @Nonnull final Class entityRawType ) + { + this.gson = gson; + this.entityRawType = entityRawType; + this.adapterFactory = adapterFactory; + + entityProperties = new LinkedHashMap<>(); + for( final Field entityField : entityRawType.getDeclaredFields() ) { + if( entityField.isAnnotationPresent(ElementName.class) + || entityField.isAnnotationPresent(SerializedName.class) ) { + + TypeAdapter fieldAdapter = null; + + // Don't get the adapter yet if the field is a navigation property. + // Otherwise an infinite loop can happen if there are circular navigation properties. + if( !VdmObject.class.isAssignableFrom(entityField.getType()) + && !Iterable.class.isAssignableFrom(entityField.getType()) ) { + + fieldAdapter = getAdapterFromField(entityField, gson); + } + + final String entityFieldKey = + entityField.isAnnotationPresent(ElementName.class) + ? entityField.getAnnotation(ElementName.class).value() + : entityField.getAnnotation(SerializedName.class).value(); + + entityProperties.put(entityFieldKey, new PropertySerializationInfo(entityField, fieldAdapter)); + } + } + + customFieldAdapter = new GsonCustomFieldAdapter(gson); + + final Class entityRawSuperType = entityRawType.getSuperclass(); + if( Object.class == entityRawSuperType ) { + delegateAdapter = + (TypeAdapter>) gson.getDelegateAdapter(adapterFactory, TypeToken.get(entityRawType)); + } else { + superClassAdapter = new GsonVdmEntityAdapter<>(adapterFactory, gson, entityRawSuperType); + } + } + + /** + * For internal use only by data model classes. + * + * @param jsonReader + * The JsonReader reference. + * @return The deserialized entity instance. + * @throws IOException + * When deserialization failed. + */ + @Override + @Nullable + public VdmObject read( @Nonnull final JsonReader jsonReader ) + throws IOException + { + try { + @SuppressWarnings( "unchecked" ) + final VdmObject entity = (VdmObject) entityRawType.getDeclaredConstructor().newInstance(); + + if( jsonReader.peek() == JsonToken.BEGIN_OBJECT ) { + jsonReader.beginObject(); + + while( jsonReader.hasNext() ) { + final String propertyKey = jsonReader.nextName(); + + if( "__metadata".equals(propertyKey) ) { + jsonReader.skipValue(); + } else if( "__deferred".equals(propertyKey) ) { + jsonReader.skipValue(); + jsonReader.endObject(); + return null; + } else { + final PropertySerializationInfo propertyInfo = getPropertySerializationInfo(propertyKey); + if( propertyInfo != null ) { + final Field entityField = propertyInfo.getJavaField(); + TypeAdapter fieldAdapter = propertyInfo.getFieldAdapter(); + + if( fieldAdapter == null ) { + fieldAdapter = getAdapterFromField(entityField, gson); + } + + if( fieldAdapter != null ) { + final Object attributeValue = fieldAdapter.read(jsonReader); + + // To be safe/secure, since fields are declared private in the VDM. + final boolean oldAccessibleValue = entityField.canAccess(entity); + entityField.setAccessible(true); + entityField.set(entity, attributeValue); + entityField.setAccessible(oldAccessibleValue); + } + } else { + final Object customValue = customFieldAdapter.read(jsonReader); + handleCustomField(entity, propertyKey, customValue); + } + } + } + + jsonReader.endObject(); + } else if( jsonReader.peek() == JsonToken.NULL ) { + jsonReader.nextNull(); + return null; + } + + return entity; + } + catch( final + InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e ) { + log + .error( + "Could not instantiate or initialize '" + + entityRawType.getName() + + "'. " + + "Returning null instead.", + e); + } + return null; + } + + private void handleCustomField( final VdmObject object, final String name, final Object value ) + { + if( Arrays.asList(VdmObject.ODATA_TYPE_ANNOTATIONS).contains(name) ) { + // do nothing + return; + } + if( object instanceof VdmEntity && Arrays.asList(VdmObject.ODATA_VERSION_ANNOTATIONS).contains(name) ) { + ((VdmEntity) object).setVersionIdentifier(value.toString()); + return; + } + object.setCustomField(name, value); + } + + @Nullable + private PropertySerializationInfo getPropertySerializationInfo( final String propertyKey ) + { + if( entityProperties.containsKey(propertyKey) ) { + return entityProperties.get(propertyKey); + } + if( superClassAdapter != null ) { + return superClassAdapter.getPropertySerializationInfo(propertyKey); + } + return null; + } + + /** + * For internal use only by data model classes. + * + * @param out + * The JsonWriter reference. + * @param value + * The entity instance to be serialized. + * @throws IOException + * When serialization failed. + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final VdmObject value ) + throws IOException + { + if( value != null ) { + final JsonObject entityAsJson = getEntityAsJsonObject(value); + final JsonObject customFieldsAsJson = gson.toJsonTree(value.getCustomFields()).getAsJsonObject(); + + for( final Map.Entry annotationProperty : value.getAnnotationProperties().entrySet() ) { + entityAsJson.add(annotationProperty.getKey(), gson.toJsonTree(annotationProperty.getValue())); + } + + for( final Map.Entry customField : customFieldsAsJson.entrySet() ) { + entityAsJson.add(customField.getKey(), customField.getValue()); + } + + gson.toJson(entityAsJson, out); + } else { + out.nullValue(); + } + } + + @SuppressWarnings( "unchecked" ) + private JsonObject getEntityAsJsonObject( final VdmObject value ) + { + if( delegateAdapter != null ) { + return delegateAdapter.toJsonTree(value).getAsJsonObject(); + } else { + final JsonObject entityAsJson = superClassAdapter.getEntityAsJsonObject(value); + for( final Map.Entry entityProperty : entityProperties.entrySet() ) { + try { + final PropertySerializationInfo serializationInfo = entityProperty.getValue(); + + // To be safe/secure, since fields are declared private in the VDM. + final Field propertyField = serializationInfo.getJavaField(); + final boolean oldAccessibleValue = propertyField.canAccess(value); + propertyField.setAccessible(true); + final Object propertyValue = propertyField.get(value); + propertyField.setAccessible(oldAccessibleValue); + + TypeAdapter fieldAdapter = (TypeAdapter) serializationInfo.getFieldAdapter(); + + if( fieldAdapter == null ) { + fieldAdapter = (TypeAdapter) getAdapterFromField(propertyField, gson); + } + + final JsonElement propertyValueAsJson = + (fieldAdapter != null) ? fieldAdapter.toJsonTree(propertyValue) : null; + + // Overwrites JSON property from the superclass if this class has a property with the same name. + entityAsJson.add(entityProperty.getKey(), propertyValueAsJson); + } + catch( final IllegalAccessException e ) { + log + .error( + "Could not serialize property '" + + entityProperty.getKey() + + "'. " + + "Returning null instead.", + e); + } + } + + // odata type + final String odataType = "#" + value.getOdataType(); + entityAsJson.addProperty(VdmObject.ODATA_TYPE_ANNOTATIONS[0], odataType); + + // odata version + if( value instanceof VdmEntity ) { + final Option versionIdentifier = ((VdmEntity) value).getVersionIdentifier(); + if( versionIdentifier.isDefined() ) { + entityAsJson.addProperty(VdmObject.ODATA_VERSION_ANNOTATIONS[0], versionIdentifier.get()); + } + } + + return entityAsJson; + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityListAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityListAdapter.java new file mode 100644 index 0000000000..a56b263c47 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEntityListAdapter.java @@ -0,0 +1,117 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * For internal use only by data model classes. + * + * @param + * The entity type. + */ +public class GsonVdmEntityListAdapter extends TypeAdapter> +{ + @Nonnull + private final Gson gson; + @Nonnull + private final TypeAdapter entityAdapter; + + /** + * For internal use only by data model classes. + * + * @param gson + * The GSON instance. + * @param entityAdapter + * The entity adapter. + */ + public GsonVdmEntityListAdapter( @Nonnull final Gson gson, @Nonnull final TypeAdapter entityAdapter ) + { + this.gson = gson; + this.entityAdapter = entityAdapter; + } + + private List readArray( @Nonnull final JsonReader in ) + throws IOException + { + in.beginArray(); + final List entityList = new ArrayList<>(); + + while( in.hasNext() ) { + final T entity = entityAdapter.read(in); + entityList.add(entity); + } + + in.endArray(); + return entityList; + } + + /** + * For internal use only by data model classes. + * + * @param in + * The JsonReader reference. + * @return The deserialized List of entities. + * @throws IOException + * When deserialization failed. + */ + @Override + @Nullable + public List read( @Nonnull final JsonReader in ) + throws IOException + { + List entityList = null; + if( in.peek() == JsonToken.BEGIN_OBJECT ) { + in.beginObject(); + if( in.peek() == JsonToken.NAME ) { + final String resultsKey = in.nextName(); + if( "results".equals(resultsKey) && in.peek() == JsonToken.BEGIN_ARRAY ) { + entityList = readArray(in); + } else { + in.skipValue(); + } + } + in.endObject(); + } else if( in.peek() == JsonToken.BEGIN_ARRAY ) { + entityList = readArray(in); + } else { + in.skipValue(); + } + return entityList; + } + + /** + * For internal use only by data model classes. + * + * @param out + * The JsonWriter reference. + * @param entityList + * The list of entities. + * @throws IOException + * When serialization failed. + */ + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final List entityList ) + throws IOException + { + if( entityList != null ) { + final JsonArray entityListAsJson = new JsonArray(); + for( final T entity : entityList ) { + entityListAsJson.add(entityAdapter.toJsonTree(entity)); + } + gson.toJson(entityListAsJson, out); + } else { + out.nullValue(); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEnumAdapter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEnumAdapter.java new file mode 100644 index 0000000000..54a8d8c67e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/GsonVdmEnumAdapter.java @@ -0,0 +1,62 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.util.Arrays; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +/** + * The VdmEnum type adapter for serializing and deserializing JSON payload. + * + * @param + * The generic type for which the adapter is applied. + */ +class GsonVdmEnumAdapter extends TypeAdapter +{ + private final ImmutableMap nameEnumLookupMap; + + GsonVdmEnumAdapter( @Nonnull final Class enumType ) + { + final VdmEnum[] constants = enumType.getEnumConstants(); + if( constants == null ) { + throw new IllegalArgumentException("Enum type " + enumType.getSimpleName() + " has no enum constants."); + } + this.nameEnumLookupMap = Maps.uniqueIndex(Arrays.asList(constants), VdmEnum::getName); + } + + @Override + public void write( @Nonnull final JsonWriter out, @Nullable final T value ) + throws IOException + { + if( value == null ) { + out.nullValue(); + } else { + final String name = value.getName(); + out.value(name); + } + } + + @Override + @SuppressWarnings( "unchecked" ) + @Nullable + public T read( @Nonnull final JsonReader in ) + throws IOException + { + if( in.peek() == JsonToken.NULL ) { + in.nextNull(); + return null; + } + final String name = in.nextString(); + final VdmEnum value = nameEnumLookupMap.getOrDefault(name, null); + return (T) value; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumDeserializer.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumDeserializer.java new file mode 100644 index 0000000000..4cebbc98ef --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumDeserializer.java @@ -0,0 +1,83 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.util.Arrays; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +import io.vavr.control.Option; +import lombok.extern.slf4j.Slf4j; + +/** + * Jackson deserializer adapter for {@link VdmEnum} types. + */ +@Slf4j +public class JacksonVdmEnumDeserializer extends StdDeserializer implements ContextualDeserializer +{ + private static final long serialVersionUID = 3282694560867458205L; + private ImmutableMap nameEnumLookupMap = ImmutableMap.of(); + private Class valueType; + + /** + * Default constructor. + */ + protected JacksonVdmEnumDeserializer() + { + super((Class) null); + } + + @Override + @Nonnull + public + JacksonVdmEnumDeserializer + createContextual( @Nonnull final DeserializationContext ctxt, @Nullable final BeanProperty property ) + { + final JavaType valueType = + Option + .of(property) + .map(BeanProperty::getType) + .map(t -> t.containedType(0)) + .getOrElse(ctxt::getContextualType); + final JacksonVdmEnumDeserializer deserializer = new JacksonVdmEnumDeserializer(); + + @SuppressWarnings( "unchecked" ) + final Class enumType = (Class) valueType.getRawClass(); + final VdmEnum[] constants = enumType.getEnumConstants(); + if( constants == null ) { + throw new IllegalStateException("Enum type " + enumType.getSimpleName() + " has no enum constants."); + } + deserializer.valueType = enumType; + deserializer.nameEnumLookupMap = Maps.uniqueIndex(Arrays.asList(constants), VdmEnum::getName); + return deserializer; + } + + @Override + @Nullable + public VdmEnum deserialize( @Nonnull final JsonParser parser, @Nonnull final DeserializationContext ctxt ) + throws IOException + { + final JsonToken currentToken = parser.currentToken(); + if( currentToken == JsonToken.VALUE_NULL ) { + return null; + } + if( currentToken != JsonToken.VALUE_STRING ) { + throw new IOException( + String.format("Failed to deserialize enum type %s from token kind %s", valueType, currentToken)); + } + + final String serializedValue = parser.getValueAsString(); + return nameEnumLookupMap.getOrDefault(serializedValue, null); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumSerializer.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumSerializer.java new file mode 100644 index 0000000000..b37bad844a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmEnumSerializer.java @@ -0,0 +1,58 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +import lombok.extern.slf4j.Slf4j; + +/** + * Jackson serializer adapter for {@link VdmEnum} types. + */ +@Slf4j +public class JacksonVdmEnumSerializer extends StdSerializer implements ContextualSerializer +{ + private static final long serialVersionUID = -5794503059647076980L; + + /** + * Default constructor. + */ + protected JacksonVdmEnumSerializer() + { + super((Class) null); + } + + @Override + public void serialize( + @Nullable final VdmEnum object, + @Nonnull final JsonGenerator gen, + @Nonnull final SerializerProvider prov ) + throws IOException + { + if( object == null ) { + gen.writeNull(); + return; + } + final String enumName = object.getName(); + gen.writeString(enumName); + } + + @Override + @Nonnull + public + JsonSerializer + createContextual( @Nonnull final SerializerProvider prov, @Nullable final BeanProperty property ) + { + final JacksonVdmEnumSerializer serializer = new JacksonVdmEnumSerializer(); + return serializer; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectDeserializer.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectDeserializer.java new file mode 100644 index 0000000000..27e1c12ac9 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectDeserializer.java @@ -0,0 +1,242 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.AnnotationMap; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmObject; +import com.sap.cloud.sdk.result.ElementName; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import io.vavr.control.Option; +import lombok.extern.slf4j.Slf4j; + +/** + * Jackson deserializer adapter for {@link VdmEntity} and {@link VdmObject} types. + */ +@Slf4j +public class JacksonVdmObjectDeserializer extends StdDeserializer> implements ContextualDeserializer +{ + private static final long serialVersionUID = -8440640969356203625L; + private JavaType valueType; + + /** + * Default constructor. + */ + protected JacksonVdmObjectDeserializer() + { + super((Class>) null); + } + + @Override + @Nonnull + public + JacksonVdmObjectDeserializer + createContextual( @Nonnull final DeserializationContext ctxt, @Nullable final BeanProperty property ) + { + final Option beanType = Option.of(property).map(BeanProperty::getType).map(t -> t.containedType(0)); + final JavaType valueType = beanType.getOrElse(ctxt::getContextualType); + final JacksonVdmObjectDeserializer deserializer = new JacksonVdmObjectDeserializer(); + deserializer.valueType = valueType; + return deserializer; + } + + @Override + @Nullable + public VdmObject deserialize( @Nonnull final JsonParser parser, @Nonnull final DeserializationContext ctxt ) + throws IOException + { + final Class vdmObjectType = valueType.getRawClass(); + final VdmObject vdmObject = instantiateVdmObject(vdmObjectType); + + // Handle current (first) JSON token + final JsonToken firstToken = parser.currentToken(); + if( firstToken == JsonToken.VALUE_NULL ) { + log.trace("VDM object is null"); + return null; + } + if( firstToken != JsonToken.START_OBJECT ) { + log.debug("VDM object can only be deserialized from JSON object."); + throw new IOException( + "Expected Start of JSON Object when deserializing an VDM object. Instead there was " + firstToken); + } + + // Create mapping for OData element name to actual Java field(s) + final Map> fieldValues = + Arrays + .stream(vdmObjectType.getDeclaredFields()) + .filter(f -> f.getAnnotation(ElementName.class) != null) + .collect(Collectors.groupingBy(f -> f.getDeclaredAnnotation(ElementName.class).value())); + + // Iterate JSON token handling + for( JsonToken token = parser.nextToken(); token != JsonToken.END_OBJECT; token = parser.nextToken() ) { + if( token != JsonToken.FIELD_NAME ) { + throw new IOException( + "Expected field name at current position of JSON object. Instead there was " + token); + } + final String fieldName = parser.currentName(); + + // Step from JSON element name to element value + parser.nextToken(); + + // Handle custom value when its field name is not declared on VDM object definition + if( !fieldValues.containsKey(fieldName) ) { + final Object value = ctxt.readValue(parser, Object.class); + handleCustomValue(vdmObject, fieldName, value); + continue; + } + + // Handle value for declared VDM object field + Object fieldValue = null; + for( final Field field : fieldValues.get(fieldName) ) { + if( fieldValue == null ) { + fieldValue = getVdmObjectFieldValue(field, parser, ctxt); + } + setVdmObjectFieldValue(vdmObject, field, fieldValue); + } + } + return vdmObject; + } + + @Nonnull + private VdmObject instantiateVdmObject( @Nonnull final Class vdmObjectType ) + throws IOException + { + try { + final Constructor declaredConstructor = vdmObjectType.getDeclaredConstructor(); + return (VdmObject) declaredConstructor.newInstance(); + } + catch( final + NoSuchMethodException + | InstantiationException + | IllegalArgumentException + | SecurityException + | IllegalAccessException + | InvocationTargetException e ) { + throw new IOException("Failed to create an instance from VDM object of type " + vdmObjectType, e); + } + } + + @Nullable + private Object getVdmObjectFieldValue( + @Nonnull final Field field, + @Nonnull final JsonParser parser, + @Nonnull final DeserializationContext ctxt ) + throws IOException + { + final JsonToken valueToken = parser.currentToken(); + + final JsonDeserialize customDeserialize = field.getAnnotation(JsonDeserialize.class); + if( customDeserialize != null ) { + try { + final AnnotationMap annotations = AnnotationMap.of(JsonDeserialize.class, customDeserialize); + final AnnotatedField annotated = new AnnotatedField(null, field, annotations); + final Object rawDeserializerType = ctxt.getAnnotationIntrospector().findDeserializer(annotated); + final Object rawDeserializer = ((Class) rawDeserializerType).getDeclaredConstructor().newInstance(); + @SuppressWarnings( "unchecked" ) + final JsonDeserializer deserializer = (JsonDeserializer) rawDeserializer; + return deserializer.deserialize(parser, ctxt); + } + catch( final Exception e ) { + final String msg = + String.format("Failed to use custom deserializer %s for field %s.", customDeserialize, field); + log.debug(msg, e); + throw new IOException(msg, e); + } + } + + if( valueToken == JsonToken.VALUE_STRING ) { + final String fieldValueString = parser.getText(); + final Option convertedObject = withCustomConverter(field.getType(), fieldValueString); + if( convertedObject.isDefined() ) { + return convertedObject.get(); + } + } + if( valueToken == JsonToken.VALUE_NULL ) { + return null; + } else { + final JavaType javaType = ctxt.constructType(field.getGenericType()); + return ctxt.readValue(parser, javaType); + } + } + + private void setVdmObjectFieldValue( + @Nonnull final VdmObject vdmObject, + @Nonnull final Field field, + @Nullable final Object value ) + throws IOException + { + try { + field.setAccessible(true); + field.set(vdmObject, value); + } + catch( final IllegalArgumentException | SecurityException | IllegalAccessException e ) { + final String className = vdmObject.getClass().getSimpleName(); + final String msg = String.format("Failed to set value for field %s on instance of %s", field, className); + log.debug(msg, e); + throw new IOException(msg, e); + } + } + + private void handleCustomValue( + @Nonnull final VdmObject object, + @Nonnull final String name, + @Nullable final Object value ) + { + if( Arrays.asList(VdmObject.ODATA_TYPE_ANNOTATIONS).contains(name) ) { + // do nothing + return; + } + if( object instanceof VdmEntity && Arrays.asList(VdmObject.ODATA_VERSION_ANNOTATIONS).contains(name) ) { + ((VdmEntity) object).setVersionIdentifier(String.valueOf(value)); + return; + } + object.setCustomField(name, value); + } + + @Nonnull + private Option withCustomConverter( @Nonnull final Class targetType, @Nonnull final String value ) + throws IOException + { + final Optional> matchingConverter = + Arrays + .stream(ODataGenericConverter.DEFAULT_CONVERTERS) + .filter(c -> targetType.isAssignableFrom(c.getType())) + .findFirst(); + + if( !matchingConverter.isPresent() ) { + return Option.none(); + } + + @SuppressWarnings( "unchecked" ) + final ConvertedObject convertedObject = + matchingConverter + .map(oDataGenericConverter -> ((ODataGenericConverter) oDataGenericConverter).fromDomain(value)) + .orElseGet(ConvertedObject::ofNotConvertible); + if( convertedObject.isNotConvertible() ) { + throw new IOException(String.format("Failed to convert %s to %s", value, targetType.getSimpleName())); + } + return Option.of(convertedObject.get()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectSerializer.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectSerializer.java new file mode 100644 index 0000000000..9fdc394243 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/JacksonVdmObjectSerializer.java @@ -0,0 +1,176 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.AnnotationMap; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmObject; +import com.sap.cloud.sdk.result.ElementName; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.extern.slf4j.Slf4j; + +/** + * Jackson serializer adapter for {@link VdmEntity} and {@link VdmObject} types. + */ +@Slf4j +public class JacksonVdmObjectSerializer extends StdSerializer> implements ContextualSerializer +{ + private static final long serialVersionUID = 3559044362941940279L; + + /** + * Default constructor. + */ + protected JacksonVdmObjectSerializer() + { + super((Class>) null); + } + + @Override + public void serialize( + @Nullable final VdmObject object, + @Nonnull final JsonGenerator gen, + @Nonnull final SerializerProvider prov ) + throws IOException + { + if( object == null ) { + gen.writeNull(); + return; + } + final boolean nonNull = + prov.getDefaultPropertyInclusion(object.getType()).getContentInclusion() == JsonInclude.Include.NON_NULL; + + gen.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); + gen.writeStartObject(); + + // odata type + final String odataType = "#" + object.getOdataType(); + gen.writeStringField(VdmObject.ODATA_TYPE_ANNOTATIONS[0], odataType); + + // odata version + if( object instanceof VdmEntity ) { + final Option versionIdentifier = ((VdmEntity) object).getVersionIdentifier(); + if( versionIdentifier.isDefined() ) { + gen.writeStringField(VdmObject.ODATA_TYPE_ANNOTATIONS[0], versionIdentifier.get()); + } + } + + final Map> fieldValues = + Arrays + .stream(object.getClass().getDeclaredFields()) + .filter(f -> f.getAnnotation(ElementName.class) != null) + .collect(Collectors.toMap(Function.identity(), f -> getFieldValue(f, object))); + + for( final Map.Entry> propertyEntry : fieldValues.entrySet() ) { + final Option propertyValueOption = propertyEntry.getValue(); + final Field propertyField = propertyEntry.getKey(); + if( propertyValueOption.isEmpty() ) { + log.trace("Field value for {} is empty.", propertyField); + if( !nonNull ) { + gen.writeNull(); + } + } else { + final Object propertyValue = propertyValueOption.get(); + writePropertyValue(propertyField, propertyValue, nonNull, gen, prov); + } + } + gen.writeEndObject(); + } + + private void writePropertyValue( + @Nonnull final Field propertyField, + @Nullable final Object propertyValue, + final boolean isNonNull, + @Nonnull final JsonGenerator gen, + @Nonnull final SerializerProvider prov ) + throws IOException + { + final String propertyName = propertyField.getAnnotation(ElementName.class).value(); + final JsonSerialize customSerialize = propertyField.getAnnotation(JsonSerialize.class); + if( customSerialize != null ) { + try { + gen.writeFieldName(propertyName); + final AnnotationMap annotations = AnnotationMap.of(JsonSerialize.class, customSerialize); + final AnnotatedField annotated = new AnnotatedField(null, propertyField, annotations); + final Object rawSerializerType = prov.getAnnotationIntrospector().findSerializer(annotated); + final Object rawSerializer = ((Class) rawSerializerType).getDeclaredConstructor().newInstance(); + @SuppressWarnings( "unchecked" ) + final JsonSerializer serializer = (JsonSerializer) rawSerializer; + serializer.serialize(propertyValue, gen, prov); + return; + } + catch( final Exception e ) { + final String msg = + String + .format( + "Failed to use custom serializer %s for field %s and value %s", + customSerialize, + propertyField, + propertyValue); + log.debug(msg, e); + throw new IOException(msg, e); + } + } + + final ConvertedObject convertedValue = withCustomConverter(propertyValue); + if( convertedValue.isConvertible() ) { + final String value = convertedValue.get(); + if( value != null || !isNonNull ) { + gen.writeStringField(propertyName, value); + } + } else { + if( propertyValue != null || !isNonNull ) { + prov.defaultSerializeField(propertyName, propertyValue, gen); + } + } + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + private ConvertedObject withCustomConverter( @Nullable final Object v ) + { + final Optional> matchingConverter = + Arrays.stream(ODataGenericConverter.DEFAULT_CONVERTERS).filter(c -> c.getType().isInstance(v)).findFirst(); + return matchingConverter + .map(oDataGenericConverter -> ((ODataGenericConverter) oDataGenericConverter).toDomain(v)) + .orElseGet(ConvertedObject::ofNotConvertible); + } + + @Nonnull + private Option getFieldValue( @Nonnull final Field f, @Nonnull final VdmObject object ) + { + return Try.of(() -> { + f.setAccessible(true); + return f.get(object); + }).onFailure(e -> log.error("Failed to get value for field {} in object {}", f, object, e)).toOption(); + } + + @Override + @Nonnull + public + JsonSerializer + createContextual( @Nonnull final SerializerProvider prov, @Nullable final BeanProperty property ) + { + return new JacksonVdmObjectSerializer(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java new file mode 100644 index 0000000000..fb0639c1ab --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/ODataGenericConverter.java @@ -0,0 +1,102 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.UUID; +import java.util.function.Function; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.typeconverter.AbstractTypeConverter; +import com.sap.cloud.sdk.typeconverter.ConvertedObject; + +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Generic fluent helper converters for String based OData V4 primitives. + * + * @param + * The Java type to which conversion happens. + */ +@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)); + + 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)); + + 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)); + + private static final ODataGenericConverter GUID = + new ODataGenericConverter<>(UUID.class, UUID::toString, UUID::fromString); + + private static final ODataGenericConverter DURATION = + new ODataGenericConverter<>(Duration.class, Duration::toString, Duration::parse); + + private static final ODataGenericConverter BINARY = + new ODataGenericConverter<>( + byte[].class, + Base64.getEncoder()::encodeToString, + ODataGenericConverter::decodeBinary); + + private static final ODataGenericConverter STRING = + 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 }; + + @Getter + private final Class type; + + @Getter + private final Class domainType = String.class; + + private final Function serializer; + private final Function deserializer; + + @Nonnull + private static byte[] decodeBinary( @Nonnull final String value ) + { + // Normalize URL-safe characters to standard Base64 + final String normalized = value.replace('-', '+').replace('_', '/'); + + return Base64.getDecoder().decode(normalized); + } + + @Nonnull + @Override + public ConvertedObject toDomainNonNull( @Nonnull final JavaT object ) + { + return ConvertedObject.of(serializer.apply(object)); + } + + @Nonnull + @Override + 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-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractBoundOperation.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractBoundOperation.java new file mode 100644 index 0000000000..c0701dda07 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractBoundOperation.java @@ -0,0 +1,62 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +abstract class AbstractBoundOperation implements BoundOperation +{ + @Nonnull + private final Class bindingType; + @Nonnull + private final Class returnType; + @Nonnull + private final String qualifiedName; + + abstract static class AbstractBoundFunction extends AbstractBoundOperation + implements + BoundFunction + { + @Getter( AccessLevel.PUBLIC ) + @Nonnull + private final ODataFunctionParameters parameters; + + AbstractBoundFunction( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name); + parameters = ODataFunctionParameters.of(args, ODataProtocol.V4); + } + } + + abstract static class AbstractBoundAction extends AbstractBoundOperation + implements + BoundAction + { + @Getter + @Nonnull + private final Map parameters; + + AbstractBoundAction( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map parameters ) + { + super(src, target, name); + this.parameters = parameters; + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractEntityBasedRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractEntityBasedRequestBuilder.java new file mode 100644 index 0000000000..24cd952279 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractEntityBasedRequestBuilder.java @@ -0,0 +1,52 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; + +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of OData requests that operate on entities as a fluent interface for further configuring the request + * and {@link #execute(com.sap.cloud.sdk.cloudplatform.connectivity.Destination) executing} it. + * + * @param + * The specific request builder type. + * @param + * The type of the entity this OData request operates on, if any. + * @param + * The type of the result entity, if any. + */ +@Slf4j +abstract class AbstractEntityBasedRequestBuilder, EntityT extends VdmEntity, ResultT> + extends + AbstractRequestBuilder +{ + /** + * Returns a class object of the type this request builder works with. + * + * @return A class object of the handled type. + */ + @Nonnull + protected abstract Class getEntityClass(); + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityPath + * The resource path pointing to the entity (collection) this request should operate on. + */ + AbstractEntityBasedRequestBuilder( @Nonnull final String servicePath, @Nonnull final ODataResourcePath entityPath ) + { + super(servicePath, entityPath); + } + + @Nonnull + static > String getEntityCollectionFromEntityClass( + @Nonnull final Class entityClass ) + { + return new VdmEntityUtil<>(entityClass).newInstance().getEntityCollection(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractRequestBuilder.java new file mode 100644 index 0000000000..828b21007a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractRequestBuilder.java @@ -0,0 +1,167 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestListener; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataUriFactory; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of a generic OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The specific builder type. + * @param + * The type of the result entity, if any. + */ +@Slf4j +abstract class AbstractRequestBuilder, ResultT> + implements + RequestBuilder +{ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final String servicePath; + + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final ODataResourcePath resourcePath; + + /** + * A map containing the headers to be used only for the actual request of this FluentHelper implementation. + */ + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final Map> headers = new HashMap<>(); + + /** + * A map containing the custom query parameters to be used only for the actual request of this FluentHelper + * implementation. + */ + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final Map parametersForRequestOnly = new HashMap<>(); + + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final List listeners = new ArrayList<>(); + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param resourcePath + * The resource path identifying the resource to operate on. + */ + public AbstractRequestBuilder( @Nonnull final String servicePath, @Nonnull final ODataResourcePath resourcePath ) + { + this.servicePath = servicePath; + this.resourcePath = resourcePath; + } + + /** + * Get the reference to this instance. + * + * @return The FluentHelper instance. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + protected BuilderT getThis() + { + return (BuilderT) this; + } + + /** + * An error handling class that implements the error result handler interface can be attached to this request + * builder. This allows custom logic to be called when an error occurs in the {@link #execute execute} method. If + * this method is not called, then an instance of ODataRequestListener is used. Only one handler can be attached at + * a time per request builder object, so calling this multiple times will replace the handler. + * + * @param listener + * Instance of an error handler class that implements the error result handler interface. + * + * @return The same request builder with its error handler set to the provided object. + */ + @Nonnull + public BuilderT withListener( @Nonnull final ODataRequestListener listener ) + { + this.listeners.add(listener); + return getThis(); + } + + @Nonnull + @Override + public BuilderT withHeader( @Nonnull final String key, @Nullable final String value ) + { + headers.computeIfAbsent(key, k -> new ArrayList<>(1)).add(value); + return getThis(); + } + + @Nonnull + @Override + public BuilderT withHeaders( @Nonnull final Map map ) + { + map.forEach(this::withHeader); + return getThis(); + } + + /** + * Gives the option to specify custom query parameters for the request. + * + *

+ * Note: It is recommended to only use this function for query parameters which are not supported + * by the VDM by default. Using this function to bypass request builder method calls can lead to unsupported + * response handling. There is no contract on the order or priority of parameters added to the request. + *

+ * + *

+ * Example: Use the request query option $search to reduce the result set, leaving + * only entities which match the specified search expression. This feature is supported in protocol OData v4. + * + *

+     * new DefaultBusinessPartnerService().getAllBusinessPartner().withQueryParameter("$search", "Köln OR Cologne")
+     * 
+ *

+ * + * @param key + * Name of the query parameter. + * @param value + * Value of the query parameter. + * + * @return The same request builder. + */ + @Nonnull + public BuilderT withQueryParameter( @Nonnull final String key, @Nullable final String value ) + { + parametersForRequestOnly.put(key, value); + return getThis(); + } + + @Nonnull + RequestT toRequest( @Nonnull final RequestT request ) + { + getHeaders().forEach(( k, values ) -> values.forEach(v -> request.addHeader(k, v))); + + getParametersForRequestOnly() + .forEach(( key, value ) -> request.addQueryParameter(key, ODataUriFactory.encodeQuery(value))); + + getListeners().forEach(request::addListener); + + return request; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractStructuredPropertyQuery.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractStructuredPropertyQuery.java new file mode 100644 index 0000000000..33b42073a8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/AbstractStructuredPropertyQuery.java @@ -0,0 +1,112 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldUntyped; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Implementation that represents read queries and holds their state at runtime. It allows for nested queries in a + * recursive manner. The implementation is the same for queries over single entities and collections of entities. In the + * VDM the available functionality is limited by the interfaces. + * + * In order to support a fluent creation of nested queries both the entity and the parent entity type are stored via + * generics. By implementing {@link StructuredProperty} the API doesn't differentiate between selections via referencing + * navigational properties and selections via sub-queries. + * + * @param + * The generic navigation property entity source type. + * @param + * The generic navigation property entity target type. + */ +@RequiredArgsConstructor( access = AccessLevel.PACKAGE ) +abstract class AbstractStructuredPropertyQuery, EntityT extends VdmObject> + implements + ProtocolQueryRead, + StructuredProperty +{ + /** + * The delegate query. + */ + @Getter( AccessLevel.PROTECTED ) + protected final StructuredQuery delegateQuery; + + /** + * Query modifier to limit which field values of the entity {@linkplain EntityT} get fetched and populated. + * + * @param fields + * Properties of {@linkplain EntityT} to be selected. + * @return This query object with the added selections. + */ + @Nonnull + protected final AbstractStructuredPropertyQuery select( + @Nonnull final Iterable> fields ) + { + for( final Property field : fields ) { + if( field instanceof SimpleProperty || field instanceof ComplexProperty ) { + delegateQuery.select(field.getFieldName()); + continue; + + } + if( field instanceof StructuredProperty ) { + if( field instanceof ComplexPropertyQuery ) { + for( final String simpleSelector : ((ComplexPropertyQuery) field) + .getDelegateQuery() + .getSimpleSelectors() ) { + final FieldUntyped untypedField = FieldReference.ofPath(field.getFieldName(), simpleSelector); + delegateQuery.select(untypedField.getFieldName()); + } + + for( final StructuredQuery subQuery : ((ComplexPropertyQuery) field) + .getDelegateQuery() + .getComplexSelectors() ) { + + final FieldUntyped untypedField = + FieldReference.ofPath(field.getFieldName(), subQuery.getEntityOrPropertyName()); + delegateQuery + .select( + StructuredQuery + .asNestedQueryOnProperty(untypedField.getFieldName(), ODataProtocol.V4) + .select(subQuery.getSimpleSelectors().toArray(new String[0])) + .select(subQuery.getComplexSelectors().toArray(new StructuredQuery[0]))); + } + } else if( field instanceof AbstractStructuredPropertyQuery ) { + delegateQuery.select(((AbstractStructuredPropertyQuery) field).getDelegateQuery()); + } else { + delegateQuery + .select(StructuredQuery.asNestedQueryOnProperty(field.getFieldName(), ODataProtocol.V4)); + } + + continue; + } + throw new ShouldNotHappenException(); + } + return this; + } + + @Nonnull + @Override + public String getFieldName() + { + return delegateQuery.getEntityOrPropertyName(); + } + + @Nonnull + String getEncodedQueryString() + { + return delegateQuery.getEncodedQueryString(); + } + + @Nonnull + String getQueryString() + { + return delegateQuery.getQueryString(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionRequestBuilder.java new file mode 100644 index 0000000000..2227a2405a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionRequestBuilder.java @@ -0,0 +1,147 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collections; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestAction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; + +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an Action OData request as a fluent interface for further configuring the request and + * {@link #execute(com.sap.cloud.sdk.cloudplatform.connectivity.Destination) executing} it. + * + * @param + * The request builder type. + * @param + * The type of the result, if any. + */ +@Slf4j +public abstract class ActionRequestBuilder, ResultT> + extends + AbstractRequestBuilder + implements + ModificationRequestBuilder +{ + private static final Gson gson = new GsonBuilder().serializeNulls().create(); + + private static final GsonVdmAdapterFactory GSON_VDM_ADAPTER_FACTORY = new GsonVdmAdapterFactory(); + private final Map parameters; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action + */ + public ActionRequestBuilder( @Nonnull final String servicePath, @Nonnull final String actionName ) + { + this(servicePath, actionName, Collections.emptyMap()); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action. + * @param parameters + * The parameters passed to the function. + */ + public ActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String actionName, + @Nonnull final Map parameters ) + { + this(servicePath, ODataResourcePath.of(actionName), parameters); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionPath + * The path to the unbound action. + * @param parameters + * The parameters passed to the function. + */ + public ActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath actionPath, + @Nonnull final Map parameters ) + { + super(servicePath, actionPath); + this.parameters = parameters; + } + + /** + * Serializes the passed parameter with custom type adapters and returns a JsonElement. + */ + @Nonnull + private JsonElement serialize( @Nullable final T parameter ) + { + if( parameter == null ) { + return JsonNull.INSTANCE; + } + + @SuppressWarnings( "unchecked" ) + final TypeToken typeToken = TypeToken.get((Class) parameter.getClass()); + final TypeAdapter typeAdapter = GSON_VDM_ADAPTER_FACTORY.create(gson, typeToken); + + final JsonElement jsonObject; + if( typeAdapter != null ) { + jsonObject = typeAdapter.toJsonTree(parameter); + } else { + jsonObject = gson.toJsonTree(parameter); + } + return jsonObject; + } + + /** + * Creates an instance of {@link ODataRequestAction} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the action name
  • + *
  • the parameters if applicable as JSON string
  • + *
+ * + * @return An initialized {@link ODataRequestFunction}. + */ + @Override + @Nonnull + public ODataRequestAction toRequest() + { + final ODataRequestAction request = + new ODataRequestAction(getServicePath(), getResourcePath(), serializeParameters(), ODataProtocol.V4); + return super.toRequest(request); + } + + @Nonnull + private String serializeParameters() + { + final JsonObject o = new JsonObject(); + parameters.forEach(( key, value ) -> o.add(key, serialize(value))); + + return gson.toJson(o); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseCollection.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseCollection.java new file mode 100644 index 0000000000..3d8005447c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseCollection.java @@ -0,0 +1,91 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for action requests. + * + * @param + * The generic result type. + */ +@EqualsAndHashCode +@ToString( doNotUseGetters = true ) +@RequiredArgsConstructor( staticName = "of", access = AccessLevel.PUBLIC ) +@Slf4j +public final class ActionResponseCollection +{ + // lazily evaluated response entity + private volatile Option> responseResult = null; + private final Object responseResultLock = new Object(); + + @Nonnull + private final ODataRequestResultGeneric result; + + @Getter + @Nonnull + private final Class actionResultClass; + + /** + * Get the optional result parsed by the HTTP content. + * + * @return The optional result entity. + */ + @Nonnull + public Option> getResponseResult() + { + if( responseResult == null ) { + synchronized( responseResultLock ) { + if( responseResult == null ) { + responseResult = parseEntityFromResponse(); + } + } + } + return responseResult; + } + + /** + * Get the response status code. + * + * @return The integer representation of the HTTP status code. + */ + public int getResponseStatusCode() + { + return result.getHttpResponse().getCode(); + } + + /** + * Get the response headers. + * + * @return The headers of the HTTP status code. + */ + @Nonnull + public Map> getResponseHeaders() + { + return result.getAllHeaderValues(); + } + + @Nonnull + private Option> parseEntityFromResponse() + { + if( actionResultClass.equals(Void.class) ) { + return Option.none(); + } + @SuppressWarnings( "unchecked" ) + final Try> parsedEntity = Try.of(() -> result.asList(actionResultClass)); + return parsedEntity.onFailure(e -> log.debug("Failed to parse entity from HTTP response.", e)).toOption(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseSingle.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseSingle.java new file mode 100644 index 0000000000..5d3e107f58 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ActionResponseSingle.java @@ -0,0 +1,93 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for action requests. + * + * @param + * The generic result type. + */ +@EqualsAndHashCode +@ToString( doNotUseGetters = true ) +@RequiredArgsConstructor( staticName = "of", access = AccessLevel.PUBLIC ) +@Slf4j +public final class ActionResponseSingle +{ + // lazily evaluated response entity + private volatile Option responseResult = null; + private final Object responseResultLock = new Object(); + + @Nonnull + private final ODataRequestResultGeneric result; + + @Getter + @Nonnull + private final Class actionResultClass; + + /** + * Get the optional result parsed by the HTTP content. + * + * @return The optional result entity. + */ + @Nonnull + public Option getResponseResult() + { + if( responseResult == null ) { + synchronized( responseResultLock ) { + if( responseResult == null ) { + responseResult = parseEntityFromResponse(); + } + } + } + return responseResult; + } + + /** + * Get the response status code. + * + * @return The integer representation of the HTTP status code. + */ + public int getResponseStatusCode() + { + return result.getHttpResponse().getCode(); + } + + /** + * Get the response headers. + * + * @return The headers of the HTTP status code. + */ + @Nonnull + public Map> getResponseHeaders() + { + return result.getAllHeaderValues(); + } + + @Nonnull + private Option parseEntityFromResponse() + { + if( actionResultClass.equals(Void.class) ) { + return Option.none(); + } + final Try parsedEntity = Try.of(() -> result.as(actionResultClass)).peek(entity -> { + if( entity instanceof VdmEntity ) { + result.getVersionIdentifierFromHeader().peek(((VdmEntity) entity)::setVersionIdentifier); + } + }); + return parsedEntity.onFailure(e -> log.debug("Failed to parse entity from HTTP response.", e)).toOption(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchRequestBuilder.java new file mode 100644 index 0000000000..5db0212ec6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchRequestBuilder.java @@ -0,0 +1,145 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestAction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestDelete; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of an OData Batch request as a fluent interface for combining multiple data reading and modifying + * operations in one HTTP request. + */ +public class BatchRequestBuilder extends AbstractRequestBuilder + implements + ModificationRequestBuilder +{ + private final ODataRequestBatch delegate; + + @Getter( AccessLevel.PROTECTED ) + private final Supplier uuidProvider = UUID::randomUUID; + + @Nonnull + @Getter( AccessLevel.PACKAGE ) + private final Map, ODataRequestGeneric> requestMapping = new IdentityHashMap<>(); + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + */ + @SuppressWarnings( "this-escape" ) // getUuidProvider() is designed to be overridable. + public BatchRequestBuilder( @Nonnull final String servicePath ) + { + super(servicePath, ODataResourcePath.of("$batch")); + this.delegate = new ODataRequestBatch(servicePath, ODataProtocol.V4, getUuidProvider()); + } + + /** + * Add read operations to the OData batch request. + * + * @param operations + * A var-arg array of read operations. + * @return The current reference of batch request builder. + * @see GetAllRequestBuilder + * @see GetByKeyRequestBuilder + * @see FunctionRequestBuilder + */ + @Nonnull + public BatchRequestBuilder addReadOperations( @Nonnull final ReadRequestBuilder... operations ) + { + for( final ReadRequestBuilder operation : operations ) { + final ODataRequestGeneric request = operation.toRequest(); + requestMapping.put(operation, request); + if( request instanceof ODataRequestRead ) { + delegate.addRead((ODataRequestRead) request); + } else if( request instanceof ODataRequestReadByKey ) { + delegate.addReadByKey((ODataRequestReadByKey) request); + } else if( request instanceof ODataRequestFunction ) { + delegate.addFunction((ODataRequestFunction) request); + } else { + throw new IllegalArgumentException( + "Failed to add unknown type of read operation to OData batch request: " + + operation.getClass().getSimpleName()); + } + } + return this; + } + + /** + * Add modifying operations to the OData batch request as combined changeset. + * + * @param operations + * A var-arg array of modifying operations. + * @return The current reference of batch request builder. + * @see CreateRequestBuilder + * @see DeleteRequestBuilder + * @see UpdateRequestBuilder + * @see ActionRequestBuilder + */ + @Nonnull + public BatchRequestBuilder addChangeset( @Nonnull final ModificationRequestBuilder... operations ) + { + final ODataRequestBatch.Changeset changeset = delegate.beginChangeset(); + for( final ModificationRequestBuilder operation : operations ) { + final ODataRequestGeneric request = operation.toRequest(); + requestMapping.put(operation, request); + if( request instanceof ODataRequestCreate ) { + changeset.addCreate((ODataRequestCreate) request); + } else if( request instanceof ODataRequestUpdate ) { + changeset.addUpdate((ODataRequestUpdate) request); + } else if( request instanceof ODataRequestDelete ) { + changeset.addDelete((ODataRequestDelete) request); + } else if( request instanceof ODataRequestAction ) { + changeset.addAction((ODataRequestAction) request); + } else { + throw new IllegalArgumentException( + "Failed to add unknown type of modifying operation to OData batch request: " + + operation.getClass().getSimpleName()); + } + } + changeset.endChangeset(); + return this; + } + + @Override + @Nonnull + public ODataRequestBatch toRequest() + { + return super.toRequest(delegate); + } + + @Nonnull + @Override + public BatchResponse execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + @SuppressWarnings( "PMD.CloseResource" ) // The ODataRequestResultMultipartGeneric is closed by BatchResponse + final ODataRequestResultMultipartGeneric response = toRequest().execute(httpClient); + + return BatchResponse.of(response, requestMapping); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchResponse.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchResponse.java new file mode 100644 index 0000000000..d4437ac570 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BatchResponse.java @@ -0,0 +1,268 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataResponseException; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for Batch response. + * + */ +@EqualsAndHashCode +@ToString +@RequiredArgsConstructor( staticName = "of", access = AccessLevel.PACKAGE ) +@Slf4j +public final class BatchResponse implements AutoCloseable +{ + @Nonnull + private final ODataRequestResultMultipartGeneric result; + + @Nonnull + private final Map, ODataRequestGeneric> requestMapping; + + /** + * Static factory method to convert from generic response to typed response. + * + * @param response + * The generic response that should be converted. + * @param initialRequest + * The initial BatchRequest + * @return The typed (high-level) BatchResponse object. + */ + @Nonnull + public static BatchResponse of( + @Nonnull final ODataRequestResultMultipartGeneric response, + @Nonnull final BatchRequestBuilder initialRequest ) + { + return BatchResponse.of(response, initialRequest.getRequestMapping()); + } + + /** + * Get the response status code. + * + * @return The integer representation of the HTTP status code. + */ + public int getResponseStatusCode() + { + return result.getHttpResponse().getCode(); + } + + /** + * Get the response headers. + * + * @return The headers of the HTTP status code. + */ + @Nonnull + public Map> getResponseHeaders() + { + return result.getAllHeaderValues(); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic entity type. + * @return The list of entities as result of the request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public > List getReadResult( @Nonnull final GetAllRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return clientResult.asList(operation.getEntityClass()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic object type. + * @return The list of objects as result of the request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public List getReadResult( @Nonnull final CollectionValueFunctionRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return clientResult.asList(operation.getResultClass()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic entity type. + * @return The list of entities as result of the request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public > T getReadResult( @Nonnull final GetByKeyRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return clientResult.as(operation.getEntityClass()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic object type. + * @return The object as result of the request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public T getReadResult( @Nonnull final SingleValueFunctionRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return clientResult.as(operation.getResultClass()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic entity type. + * @return The generic modification response wrapper object as result of the modification request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public > ModificationResponse getModificationResult( + @Nonnull final CreateRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return ModificationResponse.of(clientResult, operation.getEntity()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic entity type. + * @return The generic modification response wrapper object as result of the modification request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public > ModificationResponse getModificationResult( + @Nonnull final UpdateRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return ModificationResponse.of(clientResult, operation.getEntity()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic entity type. + * @return The generic modification response wrapper object as result of the modification request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public > ModificationResponse getModificationResult( + @Nonnull final DeleteRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return ModificationResponse.of(clientResult, operation.getEntity()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic object type. + * @return The generic action response wrapper object as result of the OData action request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public < + T> ActionResponseSingle getModificationResult( @Nonnull final SingleValueActionRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return ActionResponseSingle.of(clientResult, operation.getResultClass()); + } + + /** + * Extract the batch item result for the provided OData request. + * + * @param operation + * The OData operation that was used in the OData batch request. + * @param + * The generic object type. + * @return The generic action response wrapper object as result of the OData action request. + * @throws ODataResponseException + * When the OData batch response cannot be parsed. + * @throws IllegalArgumentException + * When the provided request reference could not be found in the original batch request. + */ + @Nonnull + public ActionResponseCollection getModificationResult( + @Nonnull final CollectionValueActionRequestBuilder operation ) + { + final ODataRequestResultGeneric clientResult = result.getResult(requestMapping.get(operation)); + return ActionResponseCollection.of(clientResult, operation.getResultClass()); + } + + /** + * Closes the underlying HTTP response entity. + * + * @since 4.15.0 + */ + @Override + public void close() + { + result.close(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundAction.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundAction.java new file mode 100644 index 0000000000..b44bfd00d4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundAction.java @@ -0,0 +1,158 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +/** + * Interface representing an action bound to a specific type. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ +public interface BoundAction extends BoundOperation +{ + /** + * The parameters to invoke this action with. + * + * @return The parameters to invoke this action with. + */ + @Nonnull + Map getParameters(); + + /** + * Specific {@link BoundAction action} operating on a single element and returning an object, if any. + * + * @param + * The type the action is bound to. + * @param + * The type this action returns. + */ + final class SingleToSingle extends AbstractBoundOperation.AbstractBoundAction + { + /** + * Default constructor. + * + * @param src + * The type the action is bound to. + * @param target + * The type this action returns. + * @param name + * The fully qualified name of the action. + * @param parameters + * The parameters to invoke this action with. + */ + public SingleToSingle( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map parameters ) + { + super(src, target, name, parameters); + } + } + + /** + * Specific {@link BoundAction action} operating on a single element and returning a collection of objects. + * + * @param + * The type the action is bound to. + * @param + * The type this action returns. + */ + final class SingleToCollection + extends + AbstractBoundOperation.AbstractBoundAction + { + /** + * Default constructor. + * + * @param src + * The type the action is bound to. + * @param target + * The type this action returns. + * @param name + * The fully qualified name of the action. + * @param parameters + * The parameters to invoke this action with. + */ + public SingleToCollection( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map parameters ) + { + super(src, target, name, parameters); + } + } + + /** + * Specific {@link BoundAction action} operating on a collection of elements and returning an object, if any. + * + * @param + * The type the action is bound to. + * @param + * The type this action returns. + */ + final class CollectionToSingle + extends + AbstractBoundOperation.AbstractBoundAction + { + /** + * Default constructor. + * + * @param src + * The type the action is bound to. + * @param target + * The type this action returns. + * @param name + * The fully qualified name of the action. + * @param parameters + * The parameters to invoke this action with. + */ + public CollectionToSingle( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map parameters ) + { + super(src, target, name, parameters); + } + } + + /** + * Specific {@link BoundAction action} operating on a collection of element and returning a collection of objects. + * + * @param + * The type the action is bound to. + * @param + * The type this action returns. + */ + final class CollectionToCollection + extends + AbstractBoundOperation.AbstractBoundAction + { + /** + * Default constructor. + * + * @param src + * The type the action is bound to. + * @param target + * The type this action returns. + * @param name + * The fully qualified name of the action. + * @param parameters + * The parameters to invoke this action with. + */ + public CollectionToCollection( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map parameters ) + { + super(src, target, name, parameters); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunction.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunction.java new file mode 100644 index 0000000000..8fc44c2a70 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunction.java @@ -0,0 +1,739 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; + +/** + * Interface representing a function bound to a specific type. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ +public interface BoundFunction extends BoundOperation +{ + /** + * The parameters this function is invoked with. + * + * @return The parameters this function is invoked with. + */ + @Nonnull + ODataFunctionParameters getParameters(); + + /* + we need to differentiate on the type system all following dimensions: + -> Composable (true/false) + -> Src Cardinality (single/collection) + -> Target Cardinality (single/collection) + -> Target Type (primitive, complex, entity) + + in total 24 combinations == 24 classes with this approach + */ + + /** + * Interface representing a composable bound function + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + interface Composable extends BoundFunction + { + } + + /*----------------------------------------------------------*/ + /* 1 - 1 Functions */ + /*----------------------------------------------------------*/ + + /** + * Specific {@link BoundFunction function} operating on a single element and returning a primitive. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToSinglePrimitive extends SingleToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function argument names and their values + */ + public SingleToSinglePrimitive( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a single element and returning a complex type. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToSingleComplex> extends SingleToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToSingleComplex( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a single element and returning an entity. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToSingleEntity> extends SingleToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToSingleEntity( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + + /** + * Class representing a composable bound function + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + public static final class Composable> + extends + SingleToSingleEntity + implements + BoundFunction.Composable + { + + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public Composable( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + } + + /*----------------------------------------------------------*/ + /* 1 - N Functions */ + /*----------------------------------------------------------*/ + + /** + * Specific {@link BoundFunction function} operating on a single element and returning a collection of primitives. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToCollectionPrimitive extends SingleToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToCollectionPrimitive( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a single element and returning a collection of complex + * objects. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToCollectionComplex> + extends + SingleToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToCollectionComplex( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a single element and returning a collection of entities. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToCollectionEntity> + extends + SingleToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToCollectionEntity( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + + /** + * Class representing a composable bound function + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + public static final class Composable> + extends + SingleToCollectionEntity + { + + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public Composable( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + } + + /*----------------------------------------------------------*/ + /* N - 1 Functions */ + /*----------------------------------------------------------*/ + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning a primitive. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToSinglePrimitive extends CollectionToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToSinglePrimitive( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning a complex object. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToSingleComplex> + extends + CollectionToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToSingleComplex( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning an entity. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToSingleEntity> + extends + CollectionToSingle + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToSingleEntity( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + + /** + * Class representing a composable bound function + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + public static final class Composable> + extends + CollectionToSingleEntity + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public Composable( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + } + + /*----------------------------------------------------------*/ + /* N - N Functions */ + /*----------------------------------------------------------*/ + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning a collection of + * primitives. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToCollectionPrimitive extends CollectionToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToCollectionPrimitive( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning a collection of + * complex objects. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToCollectionComplex> + extends + CollectionToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToCollectionComplex( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements and returning a collection of + * entities. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToCollectionEntity> + extends + CollectionToCollection + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToCollectionEntity( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + + /** + * Class representing a composable bound function + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + public static final class Composable> + extends + CollectionToCollectionEntity + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public Composable( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + } + + /*----------------------------------------------------------*/ + /* Variants without target type info */ + /* used by "applyFunction" which doesn't care about the */ + /* target type to be in the type system */ + /*----------------------------------------------------------*/ + + /** + * Specific {@link BoundFunction function} operating on a single element returning a single element. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToSingle extends AbstractBoundOperation.AbstractBoundFunction + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToSingle( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a single element returning a collection of elements. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class SingleToCollection extends AbstractBoundOperation.AbstractBoundFunction + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public SingleToCollection( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements returning a single element. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToSingle extends AbstractBoundOperation.AbstractBoundFunction + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToSingle( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } + + /** + * Specific {@link BoundFunction function} operating on a collection of elements returning a collection of elements. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ + class CollectionToCollection + extends + AbstractBoundOperation.AbstractBoundFunction + { + /** + * Create an instance of a bound function. + * + * @param src + * The type this function is bound to. + * @param target + * The type this function returns. + * @param name + * The fully qualified name + * @param args + * Key-value-pairs of function arguments names and their values + */ + public CollectionToCollection( + @Nonnull final Class src, + @Nonnull final Class target, + @Nonnull final String name, + @Nonnull final Map args ) + { + super(src, target, name, args); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundOperation.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundOperation.java new file mode 100644 index 0000000000..db19d07b17 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundOperation.java @@ -0,0 +1,38 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +/** + * Interface representing OData operations (functions and actions) bound to a specific type. + * + * @param + * The type the function is bound to. + * @param + * The type this function returns. + */ +public interface BoundOperation +{ + /** + * The fully qualified name of the bound operation. + * + * @return The fully qualified name of bound operation. + */ + @Nonnull + String getQualifiedName(); + + /** + * The type this operations is bound to. + * + * @return The type this operations is bound to. + */ + @Nonnull + Class getBindingType(); + + /** + * The type this operations returns. + * + * @return The type this operations returns. + */ + @Nonnull + Class getReturnType(); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilder.java new file mode 100644 index 0000000000..4eec581e4c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilder.java @@ -0,0 +1,105 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of an OData action request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. This one handles actions where the return type is a collection of + * primitive or entity or complex type values. + * + * @param + * The type of the result entity, or primitive or complex type + */ +public class CollectionValueActionRequestBuilder + extends + ActionRequestBuilder, ActionResponseCollection> +{ + @Getter( AccessLevel.PROTECTED ) + private final Class resultClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action + * @param resultClass + * The expected collection return type of the action. + */ + public CollectionValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String actionName, + @Nonnull final Class resultClass ) + { + super(servicePath, actionName); + this.resultClass = resultClass; + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action + * @param parameters + * The parameters passed to the action. + * @param resultClass + * The expected collection return type of the action. + */ + public CollectionValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String actionName, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + super(servicePath, actionName, parameters); + this.resultClass = resultClass; + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionPath + * The path to the unbound action + * @param parameters + * The parameters passed to the action. + * @param resultClass + * The expected collection return type of the action. + */ + public CollectionValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath actionPath, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + super(servicePath, actionPath, parameters); + this.resultClass = resultClass; + } + + @Nonnull + @Override + public ActionResponseCollection execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + return ActionResponseCollection.of(response, resultClass); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilder.java new file mode 100644 index 0000000000..68a81b31da --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilder.java @@ -0,0 +1,107 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of an OData function request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. This one handles functions where the return type is a collection of + * primitive or entity values. + * + * @param + * The type of the result entity, if any. + */ +public class CollectionValueFunctionRequestBuilder + extends + FunctionRequestBuilder, List> +{ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final Class resultClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionName + * The name of the unbound function + * @param resultClass + * The expected collection return type of the function. + */ + public CollectionValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String functionName, + @Nonnull final Class resultClass ) + { + this(servicePath, functionName, Collections.emptyMap(), resultClass); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionName + * The name of the unbound function + * @param parameters + * The parameters passed to the function. + * @param resultClass + * The expected collection return type of the function. + */ + public CollectionValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String functionName, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + this( + servicePath, + ODataResourcePath.of(functionName, ODataFunctionParameters.of(parameters, ODataProtocol.V4)), + resultClass); + + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionPath + * The {@link ODataResourcePath} identifying the function to invoke. + * @param resultClass + * The expected collection return type of the function. + */ + CollectionValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath functionPath, + @Nonnull final Class resultClass ) + { + super(servicePath, functionPath); + this.resultClass = resultClass; + } + + @Nonnull + @Override + public List execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + return response.asList(getResultClass()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexProperty.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexProperty.java new file mode 100644 index 0000000000..93d0e12ef6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexProperty.java @@ -0,0 +1,82 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableCollection; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableComplex; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Interface representing a complex property of {@link EntityT} that holds a complex type of {@link TargetT}. + * + * @param + * Entity this property is part of. + * @param + * {@link VdmComplex} this property represents. + */ +public interface ComplexProperty, TargetT extends VdmComplex> + extends + StructuredProperty +{ + /** + * A collection of complex objects. + * + * @param + * The entity type. + * @param + * The complex type. + */ + @Getter + @RequiredArgsConstructor + class Collection, ValueT extends VdmComplex> + implements + ComplexProperty, + ProtocolQueryRead, + FilterableCollection + { + private final Class entityType; + private final String fieldName; + private final Class itemType; + + @Override + @Nonnull + @SafeVarargs + @SuppressWarnings( "varargs" ) + public final ComplexPropertyQuery select( @Nonnull final Property... fields ) + { + return ComplexPropertyQuery. onProperty(getFieldName()).select(fields); + } + } + + /** + * A navigational property to a single other entity reference. + * + * @param + * The entity type. + * @param + * The navigable entity type. + */ + @Getter + @RequiredArgsConstructor + class Single, ValueT extends VdmComplex> + implements + ComplexProperty, + ProtocolQueryRead, + FilterableComplex + { + private final Class entityType; + private final String fieldName; + private final Class itemType; + + @Override + @Nonnull + @SafeVarargs + @SuppressWarnings( "varargs" ) + public final ComplexPropertyQuery select( @Nonnull final Property... fields ) + { + return ComplexPropertyQuery. onProperty(getFieldName()).select(fields); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexPropertyQuery.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexPropertyQuery.java new file mode 100644 index 0000000000..b8e49e7a09 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ComplexPropertyQuery.java @@ -0,0 +1,50 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Arrays; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; + +/** + * Implementation that represents read queries and holds their state at runtime. It allows for nested queries in a + * recursive manner. The implementation is the same for queries over single entities and collections of entities. In the + * VDM the available functionality is limited by the interfaces. + * + * In order to support a fluent creation of nested queries both the entity and the parent entity type are stored via + * generics. By implementing {@link AbstractStructuredPropertyQuery} the API doesn't differentiate between selections + * via referencing complex properties and selections via sub-queries. + * + * @param + * {@link VdmObject} this property is part of. + * @param + * {@link VdmComplex} type this property references to. + */ +public final class ComplexPropertyQuery, PropertyT extends VdmComplex> + extends + AbstractStructuredPropertyQuery +{ + private ComplexPropertyQuery( final StructuredQuery delegateQuery ) + { + super(delegateQuery); + } + + static < + ParentEntityT extends VdmObject, EntityT extends VdmComplex> + ComplexPropertyQuery + onProperty( @Nonnull final String fieldName ) + { + return new ComplexPropertyQuery<>(StructuredQuery.asNestedQueryOnProperty(fieldName, ODataProtocol.V4)); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final ComplexPropertyQuery select( @Nonnull final Property... fields ) + { + super.select(Arrays.asList(fields)); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CountRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CountRequestBuilder.java new file mode 100644 index 0000000000..d242e4a93d --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CountRequestBuilder.java @@ -0,0 +1,137 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCount; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The generic entity type. + */ +@Slf4j +public class CountRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, Long> + implements + ProtocolQueryFilter +{ + private final NavigationPropertyCollectionQuery delegateQuery; + + @Getter( AccessLevel.PROTECTED ) + private final Class entityClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityClass + * The expected entity type. + * @param entityCollection + * The entity collection + */ + public CountRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final Class entityClass, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection), entityClass); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param resourceToCount + * {@link ODataResourcePath} that identifies the collection to count. + * @param entityClass + * The expected entity type. + */ + @SuppressWarnings( "this-escape" ) + CountRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath resourceToCount, + @Nonnull final Class entityClass ) + { + super(servicePath, resourceToCount); + this.entityClass = entityClass; + this.delegateQuery = NavigationPropertyCollectionQuery.ofRootQuery(getResourcePath().toString()); + } + + /** + * Creates an instance of the {@link ODataRequestCount} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the filters to be applied
  • + *
+ * + * @return An initialized {@link ODataRequestCount}. + */ + @Override + @Nonnull + public ODataRequestCount toRequest() + { + final ODataRequestCount request = + new ODataRequestCount( + getServicePath(), + getResourcePath(), + delegateQuery.getEncodedQueryString(), + ODataProtocol.V4); + + return super.toRequest(request); + } + + @Override + @Nonnull + public Long execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + return response.as(Long.class); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final CountRequestBuilder filter( @Nonnull final FilterableBoolean... filters ) + { + delegateQuery.filter(filters); + return this; + } + + @Override + @Nonnull + public CountRequestBuilder search( @Nonnull final String search ) + { + delegateQuery.search(search); + return this; + } + + @Override + @Nonnull + public CountRequestBuilder search( @Nonnull final SearchExpression expression ) + { + delegateQuery.search(expression); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java new file mode 100644 index 0000000000..d33a67c311 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java @@ -0,0 +1,141 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.google.gson.Gson; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataSerializationException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The type of the result entity. + */ +@Slf4j +public class CreateRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, ModificationResponse> + implements + ModificationRequestBuilder> +{ + /** + * Getter for the VDM representation of the entity to be created. + */ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final EntityT entity; + + /** + * Instantiates a {@code CreateRequestBuilder}. + * + * @param servicePath + * The service path to direct the requests to. + * @param entity + * The entity to create. + * @param entityCollection + * The entity collection + */ + public CreateRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final EntityT entity, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection), entity); + } + + /** + * Instantiates a {@code CreateRequestBuilder}. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityPath + * {@link ODataResourcePath} identifying the collection the entity should be created into. + * @param entity + * The entity to create. + */ + CreateRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath entityPath, + @Nonnull final EntityT entity ) + { + super(servicePath, entityPath); + this.entity = entity; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) getEntity().getClass(); + } + + /** + * Execute the OData create request for the provided entity. + * + * {@inheritDoc} + * + * @return The wrapped service response, exposing response headers, status code and entity references. If the HTTP + * response is not within healthy bounds, then one of the declared runtime exceptions will be thrown with + * further details. + */ + @Nonnull + @Override + public ModificationResponse execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + return ModificationResponse.of(response, getEntity()); + } + + /** + * Creates an instance of the {@link ODataRequestCreate} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the entity JSON payload
  • + *
+ * + * @return An initialized {@link ODataRequestCreate}. + * @throws ODataSerializationException + * If entity cannot be serialized for HTTP request. + */ + @Override + @Nonnull + public ODataRequestCreate toRequest() + { + try { + final String serializedEntity = new Gson().toJson(entity); + + final ODataRequestCreate request = + new ODataRequestCreate(getServicePath(), getResourcePath(), serializedEntity, ODataProtocol.V4); + + return super.toRequest(request); + } + catch( final Exception e ) { + final String msg = "Failed to serialize HTTP request entity of type " + getEntityClass().getSimpleName(); + log.debug(msg, e); + final ODataRequestGeneric request = + new ODataRequestCreate(getServicePath(), getResourcePath(), "", ODataProtocol.V4); + throw new ODataSerializationException(request, entity, msg, e); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DecimalDescriptor.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DecimalDescriptor.java new file mode 100644 index 0000000000..ae2173351e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DecimalDescriptor.java @@ -0,0 +1,28 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation indicating the precision and scale of a decimal field + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target( ElementType.FIELD ) +public @interface DecimalDescriptor { + + /** + * The associated precision of the decimal number + * + * @return The associated precision of the decimal number + */ + int precision(); + + /** + * The associated scale of the decimal number + * + * @return The associated scale of the decimal number + */ + int scale(); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java new file mode 100644 index 0000000000..68042d6100 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java @@ -0,0 +1,163 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ETagSubmissionStrategy; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestDelete; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData delete request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The type of the entity to delete. + */ +@Slf4j +public class DeleteRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, ModificationResponse> + implements + ModificationRequestBuilder> +{ + /** + * The entity object to be deleted by calling the {@link #execute(Destination)} method. + */ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final EntityT entity; + + private ETagSubmissionStrategy eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ETAG_FROM_ENTITY; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entity + * The entity to delete. + * @param entityCollection + * The entity collection + */ + public DeleteRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final EntityT entity, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection, entity.getKey()), entity); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityPath + * The {@link ODataResourcePath} that identifies the entity to delete. + * @param entity + * The entity to delete. + */ + DeleteRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath entityPath, + @Nonnull final EntityT entity ) + { + super(servicePath, entityPath); + this.entity = entity; + } + + /** + * Execute the OData delete request for the provided entity. + * + * {@inheritDoc} + * + * @return The wrapped service response, exposing response headers, status code and entity references. If the HTTP + * response is not within healthy bounds, then one of the declared runtime exceptions will be thrown with + * further details. + */ + @Nonnull + @Override + public ModificationResponse execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + return ModificationResponse.of(response, getEntity()); + } + + /** + * Creates an instance of the {@link ODataRequestDelete} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the key fields of the entity
  • + *
+ * + * @return An initialized {@link ODataRequestDelete}. + */ + @Override + @Nonnull + public ODataRequestDelete toRequest() + { + final String versionIdentifier = + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()); + + final ODataRequestDelete request = + new ODataRequestDelete(getServicePath(), getResourcePath(), versionIdentifier, ODataProtocol.V4); + + return super.toRequest(request); + } + + /** + * The delete request will ignore any version identifier present on the entity and not send an `If-Match` header. + *

+ * Warning: This might lead to a response from the remote system that the `If-Match` header is missing. + *

+ * It depends on the implementation of the remote system whether the `If-Match` header is expected. + * + * @return The same request builder that will not send the `If-Match` header in the delete request + */ + @Nonnull + public DeleteRequestBuilder disableVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_NO_ETAG; + return this; + } + + /** + * The delete request will ignore any version identifier present on the entity and delete the entity, regardless of + * any changes on the remote entity. + *

+ * Warning: Be careful with this option, as this might overwrite any changes made to the remote + * representation of this object. + * + * @return The same request builder that will ignore the version identifier of the entity to delete + */ + @Nonnull + public DeleteRequestBuilder matchAnyVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ANY_MATCH_ETAG; + return this; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) entity.getClass(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java new file mode 100644 index 0000000000..8de67f8df6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java @@ -0,0 +1,59 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestFunction; + +/** + * Representation of a non-CRUD OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The request builder type. + * @param + * The type of the result entity, if any. + */ +public abstract class FunctionRequestBuilder, ResultT> + extends + AbstractRequestBuilder + implements + ReadRequestBuilder +{ + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionPath + * The {@link ODataResourcePath} identifying the function to invoke. + */ + public FunctionRequestBuilder( @Nonnull final String servicePath, @Nonnull final ODataResourcePath functionPath ) + { + super(servicePath, functionPath); + } + + /** + * Creates an instance of {@link ODataRequestFunction} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the function name
  • + *
  • the parameters if applicable
  • + *
+ * + * @return An initialized {@link ODataRequestFunction}. + */ + @Override + @Nonnull + public ODataRequestFunction toRequest() + { + final ODataRequestFunction request = + new ODataRequestFunction(getServicePath(), getResourcePath(), null, ODataProtocol.V4); + + return super.toRequest(request); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java new file mode 100644 index 0000000000..d02c3b46b9 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java @@ -0,0 +1,258 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.List; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Streams; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FieldOrdering; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The type of the result entity. + */ +@Slf4j +public class GetAllRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, List> + implements + ProtocolQueryRead, + ProtocolQueryReadCollection, + ReadRequestBuilder> +{ + private final NavigationPropertyCollectionQuery delegateQuery; + + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final Class entityClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityClass + * The expected entity type. + * @param entityCollection + * The entity collection + */ + public GetAllRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final Class entityClass, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection), entityClass); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityPath + * {@link ODataResourcePath} identifying the entity collection to read. + * @param entityClass + * The expected entity type. + */ + @SuppressWarnings( "this-escape" ) + GetAllRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath entityPath, + @Nonnull final Class entityClass ) + { + super(servicePath, entityPath); + this.entityClass = entityClass; + this.delegateQuery = NavigationPropertyCollectionQuery.ofRootQuery(getResourcePath().toString()); + } + + /** + * Creates an instance of the {@link ODataRequestRead} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the number of entries to select (top)
  • + *
  • the number of entries to ignore (skip)
  • + *
  • the order direction
  • + *
  • the filters to be applied
  • + *
  • the fields to be selected
  • + *
+ * + * @return An initialized {@link ODataRequestRead}. + */ + @Override + @Nonnull + public ODataRequestRead toRequest() + { + final ODataRequestRead request = + new ODataRequestRead( + getServicePath(), + getResourcePath(), + delegateQuery.getEncodedQueryString(), + ODataProtocol.V4); + + return super.toRequest(request); + } + + /** + * {@inheritDoc} + *

+ * Note: If the OData service responds with service-driven pagination, then the pages will be + * iterated automatically. The returned list is an eagerly loaded aggregation of all pages. Access to a lazy loading + * result-set can be enabled through the modifiers {@link #iteratingEntities()}, {@link #streamingEntities()} and + * {@link #iteratingPages()}. + */ + @Override + @Nonnull + public List execute( @Nonnull final Destination destination ) + { + final Iterable iterableItems = iteratingEntities().execute(destination); + return Lists.newArrayList(iterableItems); // eagerly request and parse all pages of the result-set + } + + /** + * Manually explore the individual pages from the result-set. The returning object allows for memory-efficient + * consumption of all data through server-driven pagination. + * + * @return An instance of {@link RequestBuilderExecutable} with a response object to lazily iterate through the + * pages of entities. + */ + @Nonnull + public RequestBuilderExecutable>> iteratingPages() + { + return this::executeInternal; + } + + /** + * Iterate through all entities from the result-set. The individual pages of the result-set are queried lazily. The + * returning object allows for memory-efficient consumption of all data through server-driven pagination. + * + * @return An instance of {@link RequestBuilderExecutable} with a response object to lazily iterate through the + * entities. + */ + @Nonnull + public RequestBuilderExecutable> iteratingEntities() + { + // concat applies lazy evaluation so individual pages will still be loaded lazily + return destination -> Iterables.concat(executeInternal(destination)); + } + + /** + * Stream through all entities from the result-set. The individual pages of the result-set are queried lazily. The + * returning object allows for memory-efficient consumption of all data through server-driven pagination. + * + * @return An instance of {@link RequestBuilderExecutable} with a response object to lazily iterate through the + * entities. + */ + @Nonnull + public RequestBuilderExecutable> streamingEntities() + { + // concat applies lazy evaluation so individual pages will still be loaded lazily + return destination -> Streams.stream(Iterables.concat(executeInternal(destination))); + } + + /** + * Set the preferred page size of the OData response. A result-set may be split into multiple pages, each including + * a subset of the entities matching the query. + *

+ * Note: The OData service might ignore the preferred page size setting and may not use pagination + * at all. + * + * @param size + * The preferred page size + * @return This request object with the added parameter. + */ + @Nonnull + public GetAllRequestBuilder withPreferredPageSize( final int size ) + { + return withHeader("Prefer", "odata.maxpagesize=" + size); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( { "varargs" } ) + public final GetAllRequestBuilder select( @Nonnull final Property... fields ) + { + delegateQuery.select(fields); + return this; + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final GetAllRequestBuilder filter( @Nonnull final FilterableBoolean... filters ) + { + delegateQuery.filter(filters); + return this; + } + + @Override + @Nonnull + public GetAllRequestBuilder top( final int top ) + { + delegateQuery.top(top); + return this; + } + + @Override + @Nonnull + public GetAllRequestBuilder skip( final int skip ) + { + delegateQuery.skip(skip); + return this; + } + + @SafeVarargs + @Override + @Nonnull + @SuppressWarnings( "varargs" ) + public final GetAllRequestBuilder orderBy( @Nonnull final FieldOrdering... ordering ) + { + delegateQuery.orderBy(ordering); + return this; + } + + @Override + @Nonnull + public GetAllRequestBuilder search( @Nonnull final String search ) + { + delegateQuery.search(search); + return this; + } + + @Override + @Nonnull + public GetAllRequestBuilder search( @Nonnull final SearchExpression expression ) + { + delegateQuery.search(expression); + return this; + } + + @Nonnull + private Iterable> executeInternal( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + return toRequest().execute(httpClient).iteratePages(getEntityClass()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java new file mode 100644 index 0000000000..7737030658 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java @@ -0,0 +1,156 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData request to retrieve an entity by its key as a fluent interface for further configuring the + * request and {@link #execute(Destination) executing} it. + * + * @param + * The type of the result entity. + */ +@Slf4j +public class GetByKeyRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, EntityT> + implements + ProtocolQueryRead, + ReadRequestBuilder +{ + private final NavigationPropertySingleQuery delegateQuery; + + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final Class entityClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityClass + * The expected entity type. + * @param entityKey + * The composite entity key. + * @param entityCollection + * The entity collection + * @throws IllegalArgumentException + * When there is no mapping found for one of the provided Java literal in the composite key. + */ + public GetByKeyRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final Class entityClass, + @Nonnull final Map entityKey, + @Nonnull final String entityCollection ) + { + this(servicePath, entityClass, ODataEntityKey.of(entityKey, ODataProtocol.V4), entityCollection); + } + + /** + * Instantiates a request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityClass + * The expected entity type. + * @param entityKey + * The composite entity key. + * @param entityCollection + * The entity collection + */ + GetByKeyRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final Class entityClass, + @Nonnull final ODataEntityKey entityKey, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection, entityKey), entityClass); + } + + /** + * Instantiates a request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param entityClass + * The expected entity type. + * @param entityPath + * The {@link ODataResourcePath} that identifies the entity to read. + */ + @SuppressWarnings( "this-escape" ) + GetByKeyRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath entityPath, + @Nonnull final Class entityClass ) + { + super(servicePath, entityPath); + this.entityClass = entityClass; + this.delegateQuery = NavigationPropertySingleQuery.ofRootQuery(getResourcePath().toString()); + } + + /** + * Creates an instance of {@link ODataRequestReadByKey} based on the Entity class. + *

+ * The following settings are used: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the key fields of the entity
  • + *
  • the fields to be selected
  • + *
+ * + * @return An initialized {@link ODataRequestReadByKey}. + */ + @Override + @Nonnull + public ODataRequestReadByKey toRequest() + { + final ODataRequestReadByKey request = + new ODataRequestReadByKey( + getServicePath(), + getResourcePath(), + delegateQuery.getEncodedQueryString(), + ODataProtocol.V4); + + return super.toRequest(request); + } + + @Override + @Nonnull + public EntityT execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + final EntityT entity = response.as(getEntityClass()); + + response.getVersionIdentifierFromHeader().peek(entity::setVersionIdentifier); + + return entity; + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final GetByKeyRequestBuilder select( @Nonnull final Property... fields ) + { + delegateQuery.select(fields); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java new file mode 100644 index 0000000000..9dd5959545 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java @@ -0,0 +1,16 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +/** + * Interface to mark OData request types as modifying operation. + * + * @param + * The type of the request's result, if any. + * + * @see CreateRequestBuilder + * @see DeleteRequestBuilder + * @see UpdateRequestBuilder + * @see ActionRequestBuilder + */ +public interface ModificationRequestBuilder extends RequestBuilder +{ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponse.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponse.java new file mode 100644 index 0000000000..945dc7b8fe --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponse.java @@ -0,0 +1,147 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import io.vavr.control.Option; +import io.vavr.control.Try; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Generic OData service response wrapper for data modification requests. + * + * @param + * The generic entity type. + */ +@EqualsAndHashCode +@ToString +@RequiredArgsConstructor( staticName = "of", access = AccessLevel.PUBLIC ) +@Slf4j +public final class ModificationResponse> +{ + private static final Gson GSON = new Gson(); + + @Nullable + private EntityT modifiedEntity; + + @Nonnull + private Option responseEntity = Option.none(); + + @Nonnull + private final ODataRequestResultGeneric result; + + @Nonnull + private final EntityT originalRequestEntity; + + /** + * Get an updated version of the entity. If the service responded with an entity it is returned here. If the service + * didn't respond with an entity but send an @{code ETag} header, the entity is updated and returned. Otherwise a + * copy of the original, unmodified entity is returned. + * + * @return The modified entity. + */ + @Nonnull + public synchronized EntityT getModifiedEntity() + { + if( modifiedEntity == null ) { + evaluateResponse(); + } + return modifiedEntity; + } + + /** + * Get the optional response entity parsed from the HTTP content. + * + * @return The parsed entity or none. + */ + @Nonnull + public synchronized Option getResponseEntity() + { + // We synchronize the full method here because Fortify does not like double checked locking: + // https://vulncat.fortify.com/en/detail?id=desc.structural.java.code_correctness_double_checked_locking + if( modifiedEntity == null ) { + evaluateResponse(); + } + return responseEntity; + } + + @SuppressWarnings( "unchecked" ) + private void evaluateResponse() + { + responseEntity = parseEntityFromResponse(); + + // create a copy before modifying the version identifier to not change existing objects + final EntityT entityToModify = responseEntity.getOrElse(originalRequestEntity); + + // clone entity + modifiedEntity = GSON.fromJson(GSON.toJson(entityToModify), (Class) originalRequestEntity.getClass()); + + modifiedEntity.setVersionIdentifier(getUpdatedVersionIdentifier().getOrNull()); + } + + /** + * Access the original entity used to make the request. + * + * @return The original entity object used to perform an OData request. + */ + @Nonnull + public EntityT getRequestEntity() + { + return originalRequestEntity; + } + + /** + * Get the response status code. + * + * @return The integer representation of the HTTP status code. + */ + public int getResponseStatusCode() + { + return result.getHttpResponse().getCode(); + } + + /** + * Get the response headers. + * + * @return The headers of the HTTP status code. + */ + @Nonnull + public Map> getResponseHeaders() + { + return result.getAllHeaderValues(); + } + + /** + * Get the version identifier present in the response headers, the identifier of the original entity or none, if + * neither exist. + * + * @return An up to date version identifier or none. + */ + @Nonnull + public Option getUpdatedVersionIdentifier() + { + return result + .getVersionIdentifierFromHeader() + .orElse(() -> getResponseEntity().flatMap(entity -> entity.getVersionIdentifier())); + } + + @Nonnull + @SuppressWarnings( "unchecked" ) + private Option parseEntityFromResponse() + { + return Try + .of(() -> result.as((Class) originalRequestEntity.getClass())) + .onFailure(e -> log.debug("Failed to parse entity from HTTP response.", e)) + .toOption() + .peek(entity -> result.getVersionIdentifierFromHeader().peek(entity::setVersionIdentifier)); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntityCollection.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntityCollection.java new file mode 100644 index 0000000000..cc0f990e56 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntityCollection.java @@ -0,0 +1,46 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +/** + * Related interface to provide access to type-safe request builders for the generic entity type. + * + * @param + * The generic entity type for which request builders can be instantiated. + */ +public interface NavigableEntityCollection> extends OperationsOnEntityCollections +{ + /** + * Fetch multiple entities. + * + * @return A request builder to fetch multiple entities. This request builder allows methods which modify the + * underlying query to be called before executing the query itself. To perform execution, call the + * {@link GetAllRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + GetAllRequestBuilder getAll(); + + /** + * Create a new entity and save it to the OData service. + * + * @param item + * The entity object that will be created and saved. + *

+ * Constraints: Not nullable + *

+ * @return A request builder to create and save a new entity. To perform execution, call the + * {@link CreateRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + CreateRequestBuilder create( @Nonnull final EntityT item ); + + /** + * Fetch the number of entries from the entity collection matching the filter and search expressions. + * + * @return A request builder to fetch the count of entities. This request builder allows methods which modify the + * underlying query to be called before executing the query itself. To perform execution, call the + * {@link CountRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + CountRequestBuilder count(); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntitySingle.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntitySingle.java new file mode 100644 index 0000000000..362c229484 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigableEntitySingle.java @@ -0,0 +1,153 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +/** + * Related interface to provide access to type-safe request builders for the generic entity type. + * + * @param + * The generic entity type for which request builders can be instantiated. + */ +public interface NavigableEntitySingle> +{ + /** + * Fetch the current entity. + * + * @return A request builder to fetch multiple entities. This request builder allows methods which modify the + * underlying query to be called before executing the query itself. To perform execution, call the + * {@link GetByKeyRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + GetByKeyRequestBuilder get(); + + /** + * Update an existing entity and save it to the OData service. + * + * @param item + * The entity object that will be updated. + *

+ * Constraints: Not nullable + *

+ * @return A request builder to update an existing entity. To perform execution, call the + * {@link UpdateRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + UpdateRequestBuilder update( @Nonnull final EntityT item ); + + /** + * Deletes the current entity in the OData service. + * + * @return A request builder to delete an existing entity. To perform execution, call the + * {@link DeleteRequestBuilder#execute execute} method on the request builder object. + */ + @Nonnull + DeleteRequestBuilder delete(); + + /** + * Navigate to a specific navigation property of current entity type. + * + * @param property + * The navigation property (collection) to be used. + * @param + * The generic type of target entity. + * @return A request builder to access further navigation properties or to instantiate type-safe request builders. + */ + @Nonnull + > NavigableEntityCollection navigateTo( + @Nonnull final NavigationProperty.Collection property ); + + /** + * Navigate to a specific navigation property of current entity type. + * + * @param property + * The navigation property (single) to be used. + * @param + * The generic type of target entity. + * @return A request builder to access further navigation properties or to instantiate type-safe request builders. + */ + @Nonnull + > NavigableEntitySingle navigateTo( + @Nonnull final NavigationProperty.Single property ); + + /** + * Apply a bound function returning a single object to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The return type of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Nonnull + SingleValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.SingleToSingle function ); + + /** + * Apply a bound function returning a collection of objects to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The type of items in the result of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Nonnull + CollectionValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.SingleToCollection function ); + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.SingleToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Nonnull + > NavigableEntitySingle withFunction( + @Nonnull final BoundFunction.SingleToSingleEntity.Composable function ); + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.SingleToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Nonnull + > NavigableEntityCollection withFunction( + @Nonnull final BoundFunction.SingleToCollectionEntity.Composable function ); + + /** + * Apply a bound action returning a single or no object to the current element. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current element. + */ + @Nonnull + SingleValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.SingleToSingle action ); + + /** + * Apply a bound action returning a collection of elements to the current element. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current element. + */ + @Nonnull + CollectionValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.SingleToCollection action ); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationProperty.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationProperty.java new file mode 100644 index 0000000000..62063b8c3a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationProperty.java @@ -0,0 +1,74 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableCollection; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableComplex; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Interface representing a navigational property of {@link EntityT} that points towards an entity of {@link TargetT}. + * + * @param + * Entity this property is part of. + * @param + * Entity this property references. + */ +public interface NavigationProperty, TargetT extends VdmEntity> + extends + StructuredProperty +{ + /** + * A navigational property to a Collection of another entity reference. + * + * @param + * The entity type. + * @param + * The navigable entity type. + */ + @Getter + @RequiredArgsConstructor + class Collection, ValueT extends VdmEntity> + extends + NavigationPropertyCollection + implements + NavigationProperty, + FilterableCollection + { + private final Class entityType; + private final String fieldName; + private final Class itemType; + } + + /** + * A navigational property to a single other entity reference. + * + * @param + * The entity type. + * @param + * The navigable entity type. + */ + @Getter + @RequiredArgsConstructor + class Single, ValueT extends VdmEntity> + implements + NavigationProperty, + ProtocolQueryRead, + FilterableComplex + { + private final Class entityType; + private final String fieldName; + private final Class itemType; + + @Override + @Nonnull + @SafeVarargs + @SuppressWarnings( "varargs" ) + public final NavigationPropertySingleQuery select( @Nonnull final Property... fields ) + { + return NavigationPropertySingleQuery. ofSubQuery(getFieldName()).select(fields); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollection.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollection.java new file mode 100644 index 0000000000..1f99f9bbc6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollection.java @@ -0,0 +1,80 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FieldOrdering; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; + +/** + * Abstract class representing the query capabilities of a collection of structured properties of {@link EntityT} with a + * type of {@link TargetT}. + * + * @param + * Entity this property is part of. + * @param + * Entity type of the collection this property references to. + */ +public abstract class NavigationPropertyCollection, TargetT extends VdmEntity> + implements + StructuredProperty, + ProtocolQueryReadCollection, + ProtocolQueryRead +{ + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertyCollectionQuery select( + @Nonnull final Property... fields ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).select(fields); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertyCollectionQuery filter( + @Nonnull final FilterableBoolean... filters ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).filter(filters); + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery top( final int top ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).top(top); + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery skip( final int skip ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).skip(skip); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertyCollectionQuery orderBy( + @Nonnull final FieldOrdering... ordering ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).orderBy(ordering); + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery search( @Nonnull final String search ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).search(search); + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery search( @Nonnull final SearchExpression expression ) + { + return NavigationPropertyCollectionQuery. ofSubQuery(getFieldName()).search(expression); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollectionQuery.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollectionQuery.java new file mode 100644 index 0000000000..69669a66fc --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertyCollectionQuery.java @@ -0,0 +1,129 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.OrderExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FieldOrdering; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; + +/** + * Implementation that represents read queries and holds their state at runtime. It allows for nested queries in a + * recursive manner. The implementation is the same for queries over single entities and collections of entities. In the + * VDM the available functionality is limited by the interfaces. + * + * In order to support a fluent creation of nested queries both the entity and the parent entity type are stored via + * generics. By implementing {@link ProtocolQueryReadCollection} the API doesn't differentiate between selections via + * referencing navigational properties and selections via sub-queries. + * + * @param + * The generic navigation property entity source type. + * @param + * The generic navigation property entity target type. + */ +public final class NavigationPropertyCollectionQuery, EntityT extends VdmEntity> + extends + AbstractStructuredPropertyQuery + implements + ProtocolQueryReadCollection +{ + private NavigationPropertyCollectionQuery( final StructuredQuery delegateQuery ) + { + super(delegateQuery); + } + + static < + ParentEntityT extends VdmObject, EntityT extends VdmEntity> + NavigationPropertyCollectionQuery + ofRootQuery( @Nonnull final String fieldName ) + { + return new NavigationPropertyCollectionQuery<>(StructuredQuery.onEntity(fieldName, ODataProtocol.V4)); + } + + static < + ParentEntityT extends VdmObject, EntityT extends VdmEntity> + NavigationPropertyCollectionQuery + ofSubQuery( @Nonnull final String fieldName ) + { + return new NavigationPropertyCollectionQuery<>( + StructuredQuery.asNestedQueryOnProperty(fieldName, ODataProtocol.V4)); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertyCollectionQuery select( + @Nonnull final Property... fields ) + { + super.select(Arrays.asList(fields)); + return this; + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertyCollectionQuery filter( + @Nonnull final FilterableBoolean... filters ) + { + final Collection untypedFilters = new ArrayList<>(); + for( final FilterableBoolean fb : filters ) { + untypedFilters.add(fb::getExpression); + } + untypedFilters.forEach(delegateQuery::filter); + return this; + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery top( final int top ) + { + delegateQuery.top(top); + return this; + } + + @Override + @Nonnull + public NavigationPropertyCollectionQuery skip( final int skip ) + { + delegateQuery.skip(skip); + return this; + } + + @SafeVarargs + @Override + @Nonnull + public final NavigationPropertyCollectionQuery orderBy( + @Nonnull final FieldOrdering... ordering ) + { + final OrderExpression expression = FieldOrdering.toOrderExpression(ordering); + if( expression != null ) { + delegateQuery.orderBy(expression); + } + return this; + } + + @Nonnull + @Override + public NavigationPropertyCollectionQuery search( @Nonnull final String search ) + { + delegateQuery.search(SearchExpression.getDoubleQuotedString(search)); + return this; + } + + @Nonnull + @Override + public NavigationPropertyCollectionQuery search( + @Nonnull final SearchExpression expression ) + { + delegateQuery.search(expression.getTerm()); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertySingleQuery.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertySingleQuery.java new file mode 100644 index 0000000000..32e4dc87f0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/NavigationPropertySingleQuery.java @@ -0,0 +1,60 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Arrays; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.query.StructuredQuery; + +/** + * Implementation that represents read queries and holds their state at runtime. It allows for nested queries in a + * recursive manner. The implementation is the same for queries over single entities and collections of entities. In the + * VDM the available functionality is limited by the interfaces. + * + * In order to support a fluent creation of nested queries both the entity and the parent entity type are stored via + * generics. By implementing {@link NavigationProperty} the API doesn't differentiate between selections via referencing + * navigational properties and selections via sub-queries. + * + * @param + * The generic navigation property entity source type. + * @param + * The generic navigation property entity target type. + */ +public final class NavigationPropertySingleQuery, EntityT extends VdmEntity> + extends + AbstractStructuredPropertyQuery +{ + private NavigationPropertySingleQuery( final StructuredQuery delegateQuery ) + { + super(delegateQuery); + } + + static < + ParentEntityT extends VdmObject, EntityT extends VdmEntity> + NavigationPropertySingleQuery + ofRootQuery( @Nonnull final String fieldName ) + { + return new NavigationPropertySingleQuery<>(StructuredQuery.onEntity(fieldName, ODataProtocol.V4)); + } + + static < + ParentEntityT extends VdmObject, EntityT extends VdmEntity> + NavigationPropertySingleQuery + ofSubQuery( @Nonnull final String fieldName ) + { + return new NavigationPropertySingleQuery<>( + StructuredQuery.asNestedQueryOnProperty(fieldName, ODataProtocol.V4)); + } + + @Override + @SafeVarargs + @Nonnull + @SuppressWarnings( "varargs" ) + public final NavigationPropertySingleQuery select( + @Nonnull final Property... fields ) + { + super.select(Arrays.asList(fields)); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/OperationsOnEntityCollections.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/OperationsOnEntityCollections.java new file mode 100644 index 0000000000..85522a1835 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/OperationsOnEntityCollections.java @@ -0,0 +1,125 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +/** + * Invoke actions and functions on OData collections + */ +public interface OperationsOnEntityCollections +{ + /** + * Create a generic type-safe request using navigation properties on entity sets of the OData service. + * + * @param entity + * A template entity instance that contains all necessary key attributes. + *

+ * Constraints: Not nullable + *

+ * @param + * The generic entity type for which navigation properties can be accessed. + * @return A request builder to navigate the service request using the provided key fields of an entity. With + * another navigation property this object can be used to instantiate new request builders. + */ + @Nonnull + > NavigableEntitySingle forEntity( @Nonnull final EntityT entity ); + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.CollectionToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The entity type + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Nonnull + < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntitySingle + + withFunction( @Nonnull final BoundFunction.CollectionToSingleEntity.Composable function ); + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.CollectionToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The entity type + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Nonnull + + < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntityCollection + withFunction( @Nonnull final BoundFunction.CollectionToCollectionEntity.Composable function ); + + /** + * Apply a bound action returning a collection of objects to the current entity collection. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The entity type + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current elements. + */ + @Nonnull + , ResultT> CollectionValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToCollection action ); + + /** + * Apply a bound action returning a single or no object to an entity collection of the service. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current elements. + */ + @Nonnull + , ResultT> SingleValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToSingle action ); + + /** + * Apply a bound function returning a single object to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The return type of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Nonnull + , ResultT> SingleValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToSingle function ); + + /** + * Apply a bound function returning a collection of objects to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The type of items in the result of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Nonnull + , ResultT> CollectionValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToCollection function ); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/Property.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/Property.java new file mode 100644 index 0000000000..617cbee905 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/Property.java @@ -0,0 +1,13 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; + +/** + * Generic entity property. + * + * @param + * The generic entity type for this property. + */ +public interface Property extends FieldReference +{ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryFilter.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryFilter.java new file mode 100644 index 0000000000..9efc3026df --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryFilter.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; + +/** + * Interface that allows for constructing filterable OData queries (reading) over a collection of entities. + * + * @param + * The entity type to be queried. + */ +interface ProtocolQueryFilter> +{ + /** + * Filter on properties of {@linkplain EntityT}. + * + * @param filters + * Filter expressions to be added to the request. + * @return This request object with the added filters. + */ + @Nonnull + @SuppressWarnings( { "varargs", "unchecked" } ) + ProtocolQueryFilter filter( @Nonnull final FilterableBoolean... filters ); + + /** + * Request modifier to return the set of entities that contain the specified value. If this method is never called, + * then all the accessible entities will be returned. + * + * @param search + * A string value as the search criteria. + * @return The same request builder with this request modifier applied. + */ + @Nonnull + ProtocolQueryFilter search( @Nonnull final String search ); + + /** + * Request modifier to return the set of entities corresponding to the specified boolean expression. If this method + * is never called, then all the accessible entities will be returned. + * + * @param expression + * SearchExpression as the search criteria + * @return The same request builder with this request modifier applied. + */ + @Nonnull + ProtocolQueryFilter search( @Nonnull final SearchExpression expression ); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryRead.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryRead.java new file mode 100644 index 0000000000..8e6210059b --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryRead.java @@ -0,0 +1,28 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +/** + * Interface that allows for constructing OData read queries. + * + * @param + * The entity type to be queried. + */ +interface ProtocolQueryRead> +{ + /** + * Query modifier to limit which field values of the entity {@linkplain EntityT} get fetched and populated. + * Navigational properties supplied here will be expanded. If this method is called at least once, then only the + * specified fields will be fetched and populated. + * + * If none of the select methods is called, then all fields will be fetched and populated. Calling this multiple + * times will combine the set(s) of fields of each call. + * + * @param fields + * Properties of {@linkplain EntityT} to be selected. + * @return This request object with the added selections. + */ + @Nonnull + @SuppressWarnings( { "varargs", "unchecked" } ) + ProtocolQueryRead select( @Nonnull final Property... fields ); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryReadCollection.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryReadCollection.java new file mode 100644 index 0000000000..e7ddf4f34c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ProtocolQueryReadCollection.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FieldOrdering; + +/** + * Interface that allows for constructing OData queries (reading) over a collection of entities. + * + * @param + * The entity type to be queried. + */ +interface ProtocolQueryReadCollection> extends ProtocolQueryFilter +{ + /** + * Limit the number of results of this request. + * + * @param top + * The maximum amount of elements this request shall return. + * @return This request with the top parameter set. + */ + @Nonnull + ProtocolQueryReadCollection top( int top ); + + /** + * Determine the how many first N entities of the result set should be skipped. If this method is never called, then + * the full list will be returned from the first entity. If this method is called multiple times, then only the + * value of the last call will be used. + * + * @param skip + * The amount of elements this request will skip. + * @return This request with the skip parameter set. + */ + @Nonnull + ProtocolQueryReadCollection skip( int skip ); + + /** + * Sort the set of returned entities by non-complex fields. + * + * @param ordering + * Fields to sort by. + * @return This request with the orderBy parameter set. + */ + @Nonnull + @SuppressWarnings( { "varargs", "unchecked" } ) + ProtocolQueryReadCollection orderBy( @Nonnull final FieldOrdering... ordering ); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java new file mode 100644 index 0000000000..b07c804a72 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java @@ -0,0 +1,15 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +/** + * Interface to mark OData request types as reading operation. + * + * @param + * The type of the request's result, if any. + * + * @see GetAllRequestBuilder + * @see GetByKeyRequestBuilder + * @see FunctionRequestBuilder + */ +public interface ReadRequestBuilder extends RequestBuilder +{ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilder.java new file mode 100644 index 0000000000..01ba7dae09 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilder.java @@ -0,0 +1,54 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; + +/** + * Representation of a generic OData request builder as a fluent interface. + * + * @param + * The type of the result entity, if any. + */ +public interface RequestBuilder extends RequestBuilderExecutable +{ + /** + * Assemble a generic, untyped request object that represents the request build up via this builder. + * + * @return A request object extending {@link ODataRequestGeneric}. + */ + @Nonnull + ODataRequestGeneric toRequest(); + + /** + * Gives the option to specify custom HTTP headers. Multiple headers with the same key can be specified. The + * returned object allows to specify the requests the headers should be used in. + * + * @param key + * Name of the (first) desired HTTP header parameter. + * @param value + * Value of the (first) desired HTTP header parameter. + * + * @return A request builder to specify further headers and their intended usage. + */ + @Nonnull + RequestBuilder withHeader( @Nonnull final String key, @Nullable final String value ); + + /** + * Gives the option to specify a map of custom HTTP headers. The returned object allows to specify the requests the + * headers should be used in. + * + * @param map + * A map of HTTP header key/value pairs. + * @return A request builder to specify further headers and their intended usage. + */ + @Nonnull + default RequestBuilder withHeaders( @Nonnull final Map map ) + { + map.forEach(this::withHeader); + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderExecutable.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderExecutable.java new file mode 100644 index 0000000000..490465ffbd --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderExecutable.java @@ -0,0 +1,52 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; +import com.sap.cloud.sdk.cloudplatform.connectivity.exception.HttpClientInstantiationException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataException; + +import io.vavr.control.Try; + +/** + * Representation of a generic executable OData request builder as a fluent interface. + * + * @param + * The type of the result entity, if any. + */ +public interface RequestBuilderExecutable +{ + /** + * Execute the OData request. + * + * @param destination + * The destination to be used as request target. + * @return The generic OData response result. + * + * @throws DestinationAccessException + * If there is an issue accessing the {@link Destination}. + * @throws HttpClientInstantiationException + * If there is an issue creating the {@link HttpClient}. + * @throws ODataException + * If the OData request execution failed. Please find the documentation for {@link ODataException} + * possible sub-types and error scenarios they can occur in. + */ + @Nonnull + ResultT execute( @Nonnull final Destination destination ); + + /** + * Safely execute the OData request. + * + * @param destination + * The destination to be used as request target. + * @return The generic OData response result wrapped in a {@code Try} block. + */ + @Nonnull + default Try tryExecute( @Nonnull final Destination destination ) + { + return Try.of(() -> execute(destination)); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ResourcePathUtil.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ResourcePathUtil.java new file mode 100644 index 0000000000..8f44a4cbea --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ResourcePathUtil.java @@ -0,0 +1,24 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; + +class ResourcePathUtil +{ + @Nonnull + static , ResultT> ODataResourcePath ofBoundOperation( + @Nonnull final BoundOperation operation ) + { + final String entityCollection = + new VdmEntityUtil<>(operation.getBindingType()).newInstance().getEntityCollection(); + + return new ODataResourcePath().addSegment(entityCollection); + } + + @Nonnull + static > ODataResourcePath ofEntity( @Nonnull final EntityT entity ) + { + return new ODataResourcePath().addSegment(entity.getEntityCollection(), entity.getKey()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SearchExpression.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SearchExpression.java new file mode 100644 index 0000000000..1a3b8b3163 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SearchExpression.java @@ -0,0 +1,118 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Representation of a OData query parameter for Search Modifier + */ +@RequiredArgsConstructor +public class SearchExpression +{ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final String term; + + /** + * Create a search expression for a single string. + * + * @param term + * The search string + * @return Search Expression with a single search string. + */ + @Nonnull + public static SearchExpression of( @Nonnull final String term ) + { + return new SearchExpression(getDoubleQuotedString(term)); + } + + /** + * Combine current string with another search string in conjunction. + * + * @param term + * The other search string. + * @return Search Expression with a conjunction string. + */ + @Nonnull + public SearchExpression and( @Nonnull final String term ) + { + return new SearchExpression("(" + this.term + " AND " + getDoubleQuotedString(term) + ")"); + } + + /** + * Combine current search expression with another search expression in conjunction. + * + * @param searchExpression + * The other search expression. + * @return Search Expression with a conjunction. + */ + @Nonnull + public SearchExpression and( @Nonnull final SearchExpression searchExpression ) + { + return new SearchExpression("(" + this.term + " AND " + searchExpression.getTerm() + ")"); + } + + /** + * Combine current string with another search string in disjunction. + * + * @param term + * The other search string. + * @return Search Expression with a disjunction string. + */ + @Nonnull + public SearchExpression or( @Nonnull final String term ) + { + return new SearchExpression("(" + this.term + " OR " + getDoubleQuotedString(term) + ")"); + } + + /** + * Combine current search expression with another search expression in disjunction. + * + * @param searchExpression + * The other search expression. + * @return Search Expression with a disjunction. + */ + @Nonnull + public SearchExpression or( @Nonnull final SearchExpression searchExpression ) + { + return new SearchExpression("(" + this.term + " OR " + searchExpression.getTerm() + ")"); + } + + /** + * Negate the current search expression. + * + * @return Modified search expression with negation. + */ + @Nonnull + public SearchExpression not() + { + return new SearchExpression("NOT " + this.term); + } + + /** + * Escape and encapsulate String literal. + * + * @param text + * The String to be quoted. + * @return The prepared String. + */ + @Nonnull + static String getDoubleQuotedString( @Nonnull final String text ) + { + if( text.contains("&") ) { + throw new IllegalArgumentException("Search literal contains a forbidden character '&'."); + } + + // escape backslash \ -> \\ + String encodedText = text.replaceAll("\\\\", "$0$0"); + + // escape double quotes " -> \" + encodedText = encodedText.replaceAll("\"", "\\\\$0"); + + // wrap escaped string into double quotes + return "\"" + encodedText + "\""; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntities.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntities.java new file mode 100644 index 0000000000..3469a6424c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntities.java @@ -0,0 +1,215 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; + +import io.vavr.control.Option; + +/** + * OData Service interface to provide type-safe and recursive access to nested entities and their navigation properties. + */ +public interface ServiceWithNavigableEntities extends OperationsOnEntityCollections +{ + /** + * Getter for the OData service root path. + * + * @return the service path. + */ + @Nonnull + String getServicePath(); + + /** + * Create a generic type-safe request using navigation properties on entity sets of the OData service. + * + * @param entity + * A template entity instance that contains all necessary key attributes. + *

+ * Constraints: Not nullable + *

+ * @param + * The generic entity type for which navigation properties can be accessed. + * @return A request builder to navigate the service request using the provided key fields of an entity. With + * another navigation property this object can be used to instantiate new request builders. + */ + @Override + @Nonnull + default < + EntityT extends VdmEntity> NavigableEntitySingle forEntity( @Nonnull final EntityT entity ) + { + if( !VdmEntitySet.class.isAssignableFrom(entity.getType()) ) { + throw new IllegalStateException( + "Entity type " + + entity.getType().getSimpleName() + + " must be a sub class of " + + VdmEntitySet.class.getName()); + } + + final ODataResourcePath resourcePath = ResourcePathUtil.ofEntity(entity); + + return new ServiceWithNavigableEntitiesImpl.EntitySingle<>( + getServicePath(), + resourcePath, + Option.of(entity), + entity.getType()); + } + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.CollectionToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The entity type + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Override + @Nonnull + default < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntitySingle + withFunction( @Nonnull final BoundFunction.CollectionToSingleEntity.Composable function ) + { + final ODataResourcePath resourcePath = + ResourcePathUtil + .ofBoundOperation(function) + .addSegment(function.getQualifiedName(), function.getParameters()); + + return new ServiceWithNavigableEntitiesImpl.EntitySingle<>( + getServicePath(), + resourcePath, + Option.none(), + function.getReturnType()); + } + + /** + * Use a composable function as a path element in an OData request. Similar to + * {@link #applyFunction(BoundFunction.CollectionToSingle) applyFunction} but allows further path segments after the + * function. Functions must be marked as {@code composable} by the service. + * + * @param function + * The composable functions. + * @param + * The entity type + * @param + * The return type of the function. + * @return A request builder that allows for adding further path segments. + */ + @Override + @Nonnull + default < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntityCollection + withFunction( @Nonnull final BoundFunction.CollectionToCollectionEntity.Composable function ) + { + final ODataResourcePath resourcePath = + ResourcePathUtil + .ofBoundOperation(function) + .addSegment(function.getQualifiedName(), function.getParameters()); + + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + getServicePath(), + resourcePath, + function.getReturnType()); + } + + /** + * Apply a bound action returning a collection of objects to the current entity collection. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The entity type + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current elements. + */ + @Override + @Nonnull + default , ResultT> CollectionValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToCollection action ) + { + final ODataResourcePath resourcePath = ResourcePathUtil.ofBoundOperation(action); + + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + getServicePath(), + resourcePath, + action.getBindingType()).applyAction(action); + } + + /** + * Apply a bound action returning a single or no object to an entity collection of the service. + * + * @param action + * The action to apply. Actions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The return type of the action. + * @return A new request builder for the supplied action applied to the current elements. + */ + @Override + @Nonnull + default , ResultT> SingleValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToSingle action ) + { + final ODataResourcePath resourcePath = ResourcePathUtil.ofBoundOperation(action); + + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + getServicePath(), + resourcePath, + action.getBindingType()).applyAction(action); + } + + /** + * Apply a bound function returning a single object to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The return type of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Override + @Nonnull + default , ResultT> SingleValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToSingle function ) + { + final ODataResourcePath resourcePath = ResourcePathUtil.ofBoundOperation(function); + + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + getServicePath(), + resourcePath, + function.getBindingType()).applyFunction(function); + } + + /** + * Apply a bound function returning a collection of objects to the current element. + * + * @param function + * The function to apply. Functions are available on generated entity classes. + * @param + * The type this function is bound to + * @param + * The type of items in the result of the function. + * @return A new request builder for the supplied function applied to the current element. + */ + @Override + @Nonnull + default , ResultT> CollectionValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToCollection function ) + { + final ODataResourcePath resourcePath = ResourcePathUtil.ofBoundOperation(function); + + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + getServicePath(), + resourcePath, + function.getBindingType()).applyFunction(function); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntitiesImpl.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntitiesImpl.java new file mode 100644 index 0000000000..76f4eca02c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ServiceWithNavigableEntitiesImpl.java @@ -0,0 +1,308 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.HttpHeaders; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; + +import io.vavr.control.Option; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +class ServiceWithNavigableEntitiesImpl +{ + @RequiredArgsConstructor + static class EntitySingle> implements NavigableEntitySingle + { + /** + * The service path of the entity. + */ + @Nonnull + protected final String servicePath; + + /** + * The path to the entity. + */ + @Nonnull + protected final ODataResourcePath entityPath; + + /** + * The entity to be navigated. + */ + @Nonnull + protected final Option maybeEntity; + + @Nonnull + private final Class entityType; + + @Nonnull + @Override + public GetByKeyRequestBuilder get() + { + return new GetByKeyRequestBuilder<>(servicePath, entityPath, entityType); + } + + @Nonnull + @Override + public UpdateRequestBuilder update( @Nonnull final EntityT item ) + { + return new UpdateRequestBuilder<>(servicePath, entityPath, item); + } + + @Nonnull + @Override + public DeleteRequestBuilder delete() + { + return new DeleteRequestBuilder<>( + servicePath, + entityPath, + maybeEntity.getOrElse(() -> new VdmEntityUtil<>(entityType).newInstance())); + } + + @Nonnull + @Override + public > NavigableEntityCollection navigateTo( + @Nonnull final NavigationProperty.Collection property ) + { + return new EntityCollection<>( + servicePath, + entityPath.copy().addSegment(property.getFieldName()), + property.getItemType()); + } + + @Nonnull + @Override + public > NavigableEntitySingle navigateTo( + @Nonnull final NavigationProperty.Single property ) + { + return new EntitySingle<>( + servicePath, + entityPath.copy().addSegment(property.getFieldName()), + Option.none(), + property.getItemType()); + } + + @Nonnull + @Override + public SingleValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.SingleToSingle function ) + { + return newSingleValueFunctionRequestBuilder(servicePath, entityPath, function); + } + + @Nonnull + @Override + public CollectionValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.SingleToCollection function ) + { + return newCollectionValueFunctionRequestBuilder(servicePath, entityPath, function); + } + + @Override + @Nonnull + public > NavigableEntitySingle withFunction( + @Nonnull final BoundFunction.SingleToSingleEntity.Composable function ) + { + return new EntitySingle<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + Option.none(), + function.getReturnType()); + } + + @Override + @Nonnull + public > NavigableEntityCollection withFunction( + @Nonnull final BoundFunction.SingleToCollectionEntity.Composable function ) + { + return new EntityCollection<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + function.getReturnType()); + } + + @Override + @Nonnull + public SingleValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.SingleToSingle action ) + { + final ODataResourcePath actionPath = entityPath.copy().addSegment(action.getQualifiedName()); + final SingleValueActionRequestBuilder requestBuilder = + new SingleValueActionRequestBuilder<>( + servicePath, + actionPath, + action.getParameters(), + action.getReturnType()); + maybeEntity + .filter(e -> e.getVersionIdentifier().isDefined()) + .map(VdmEntity::getVersionIdentifier) + .filter(Option::isDefined) + .map(Option::get) + .forEach(eTag -> requestBuilder.withHeader(HttpHeaders.IF_MATCH, eTag)); + return requestBuilder; + } + + @Override + @Nonnull + public CollectionValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.SingleToCollection action ) + { + final ODataResourcePath actionPath = entityPath.copy().addSegment(action.getQualifiedName()); + final CollectionValueActionRequestBuilder requestBuilder = + new CollectionValueActionRequestBuilder<>( + servicePath, + actionPath, + action.getParameters(), + action.getReturnType()); + maybeEntity + .filter(e -> e.getVersionIdentifier().isDefined()) + .map(VdmEntity::getVersionIdentifier) + .filter(Option::isDefined) + .map(Option::get) + .forEach(eTag -> requestBuilder.withHeader(HttpHeaders.IF_MATCH, eTag)); + return requestBuilder; + } + } + + @RequiredArgsConstructor + static class EntityCollection> + implements + NavigableEntityCollection + { + @Getter + private final String servicePath; + private final ODataResourcePath entityPath; + private final Class navigationType; + + @Nonnull + @Override + public > NavigableEntitySingle forEntity( + @Nonnull final EntityT entity ) + { + return new EntitySingle<>( + servicePath, + entityPath.addParameterToLastSegment(entity.getKey()), + Option.of(entity), + entity.getType()); + } + + @Override + @Nonnull + public GetAllRequestBuilder getAll() + { + return new GetAllRequestBuilder<>(servicePath, entityPath, navigationType); + } + + @Override + @Nonnull + public CreateRequestBuilder create( @Nonnull final NavigationT item ) + { + return new CreateRequestBuilder<>(servicePath, entityPath, item); + } + + @Override + @Nonnull + public CountRequestBuilder count() + { + return new CountRequestBuilder<>(servicePath, entityPath, navigationType); + } + + @Nonnull + @Override + public < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntitySingle + withFunction( @Nonnull final BoundFunction.CollectionToSingleEntity.Composable function ) + { + return new ServiceWithNavigableEntitiesImpl.EntitySingle<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + Option.none(), + function.getReturnType()); + } + + @Nonnull + @Override + public < + EntityT extends VdmEntity, ResultT extends VdmEntity> + NavigableEntityCollection + withFunction( + @Nonnull final BoundFunction.CollectionToCollectionEntity.Composable function ) + { + return new ServiceWithNavigableEntitiesImpl.EntityCollection<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + function.getReturnType()); + } + + @Nonnull + @Override + public , ResultT> CollectionValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToCollection action ) + { + return new CollectionValueActionRequestBuilder<>( + servicePath, + entityPath.copy().addSegment(action.getQualifiedName()), + action.getParameters(), + action.getReturnType()); + } + + @Nonnull + @Override + public , ResultT> SingleValueActionRequestBuilder applyAction( + @Nonnull final BoundAction.CollectionToSingle action ) + { + return new SingleValueActionRequestBuilder<>( + servicePath, + entityPath.copy().addSegment(action.getQualifiedName()), + action.getParameters(), + action.getReturnType()); + } + + @Nonnull + @Override + public , ResultT> SingleValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToSingle function ) + { + return newSingleValueFunctionRequestBuilder(servicePath, entityPath, function); + } + + @Nonnull + @Override + public < + EntityT extends VdmEntity, ResultT> CollectionValueFunctionRequestBuilder applyFunction( + @Nonnull final BoundFunction.CollectionToCollection function ) + { + return newCollectionValueFunctionRequestBuilder(servicePath, entityPath, function); + } + } + + private static < + EntityT extends VdmEntity, ResultT> + SingleValueFunctionRequestBuilder + newSingleValueFunctionRequestBuilder( + final String servicePath, + final ODataResourcePath entityPath, + final BoundFunction function ) + { + return new SingleValueFunctionRequestBuilder<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + function.getReturnType()); + } + + private static < + EntityT extends VdmEntity, ResultT> + CollectionValueFunctionRequestBuilder + newCollectionValueFunctionRequestBuilder( + final String servicePath, + final ODataResourcePath entityPath, + final BoundFunction function ) + { + return new CollectionValueFunctionRequestBuilder<>( + servicePath, + entityPath.copy().addSegment(function.getQualifiedName(), function.getParameters()), + function.getReturnType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SimpleProperty.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SimpleProperty.java new file mode 100644 index 0000000000..15bcc01ee6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SimpleProperty.java @@ -0,0 +1,241 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.expression.FieldOrdering; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableCollection; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableDate; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableDateTime; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableDuration; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableEnum; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableGuid; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableNumericDecimal; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableNumericInteger; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableString; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableTime; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Simple property. + * + * @param + */ +@SuppressWarnings( "PMD.UnnecessaryFullyQualifiedName" ) // due to java.lang.String +public interface SimpleProperty extends Property +{ + /** + * A pseudo property referencing all fields. + * + * @param + * The entity type. + */ + class All implements SimpleProperty + { + @Getter + private final java.lang.String fieldName = ""; + @Getter + private final List selections = Collections.singletonList("*"); + } + + /** + * A String property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class String implements SimpleProperty, FilterableString + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Boolean property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Boolean implements SimpleProperty, FilterableBoolean + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Decimal property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class NumericDecimal implements SimpleProperty, FilterableNumericDecimal + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * An Integer property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class NumericInteger implements SimpleProperty, FilterableNumericInteger + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Guid property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Guid implements SimpleProperty, FilterableGuid + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Binary property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Binary implements SimpleProperty + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Duration property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Duration implements SimpleProperty, FilterableDuration + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A DateTime property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class DateTime implements SimpleProperty, FilterableDateTime + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Date property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Date implements SimpleProperty, FilterableDate + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A Time property. + * + * @param + * The entity type. + */ + @Getter + @RequiredArgsConstructor + class Time implements SimpleProperty, FilterableTime + { + private final Class entityType; + private final java.lang.String fieldName; + } + + /** + * A composite property holding a collection of values. + * + * @param + * The entity type. + * @param + * The collection item type. + */ + @Getter + @RequiredArgsConstructor + class Collection implements SimpleProperty, FilterableCollection + { + private final Class entityType; + private final java.lang.String fieldName; + private final Class itemType; + } + + /** + * A property with predefined possible values. + * + * @param + * The entity type. + * @param + * The Enum type. + */ + @Getter + @RequiredArgsConstructor + class Enum implements SimpleProperty, FilterableEnum + { + private final Class entityType; + private final java.lang.String fieldName; + private final java.lang.String enumType; + } + + /** + * A property for order ascending. + * + * @return The FieldOrdering which has the field and ordering. + */ + @Nonnull + default FieldOrdering asc() + { + return FieldOrdering.asc(this); + } + + /** + * A property for order descending. + * + * @return The FieldOrdering which has the field and ordering. + */ + @Nonnull + default FieldOrdering desc() + { + return FieldOrdering.desc(this); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilder.java new file mode 100644 index 0000000000..b4eaafac32 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilder.java @@ -0,0 +1,106 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of an OData action request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. This one handles actions where the return type is a single primitive or + * entity value or complex type. It also handles actions with no return type. + * + * @param + * The type of the result entity or complex type or primitive type, if any. For actions that return a single + * value or nothing, the result is wrapped inside a {@link ActionResponseSingle } instance. + */ +public class SingleValueActionRequestBuilder + extends + ActionRequestBuilder, ActionResponseSingle> +{ + @Getter( AccessLevel.PROTECTED ) + private final Class resultClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action + * @param resultClass + * The expected return type of the action. + */ + public SingleValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String actionName, + @Nonnull final Class resultClass ) + { + super(servicePath, actionName); + this.resultClass = resultClass; + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionName + * The name of the unbound action + * @param parameters + * The parameters passed to the action. + * @param resultClass + * The expected return type of the action. + */ + public SingleValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String actionName, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + super(servicePath, actionName, parameters); + this.resultClass = resultClass; + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param actionPath + * The path to the unbound action + * @param parameters + * The parameters passed to the action. + * @param resultClass + * The expected return type of the action. + */ + public SingleValueActionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath actionPath, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + super(servicePath, actionPath, parameters); + this.resultClass = resultClass; + } + + @Nonnull + @Override + public ActionResponseSingle execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + return ActionResponseSingle.of(response, resultClass); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilder.java new file mode 100644 index 0000000000..853195d434 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilder.java @@ -0,0 +1,110 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collections; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * Representation of an OData function request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. This one handles functions where the return type is a single primitive or + * entity value. + * + * @param + * The type of the result entity, if any. + */ +public class SingleValueFunctionRequestBuilder + extends + FunctionRequestBuilder, ResultT> +{ + @Nonnull + @Getter( AccessLevel.PROTECTED ) + private final Class resultClass; + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionName + * The name of the unbound function + * @param resultClass + * The expected return type of the function. + */ + public SingleValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String functionName, + @Nonnull final Class resultClass ) + { + this(servicePath, functionName, Collections.emptyMap(), resultClass); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionName + * The name of the unbound function + * @param parameters + * The parameters passed to the function. + * @param resultClass + * The expected collection return type of the function. + */ + public SingleValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final String functionName, + @Nonnull final Map parameters, + @Nonnull final Class resultClass ) + { + this( + servicePath, + ODataResourcePath.of(functionName, ODataFunctionParameters.of(parameters, ODataProtocol.V4)), + resultClass); + } + + /** + * Instantiates this request builder using the given service path to send the requests. + * + * @param servicePath + * The service path to direct the requests to. + * @param functionPath + * The {@link ODataResourcePath} identifying the function to invoke. + * @param resultClass + * The expected collection return type of the function. + */ + SingleValueFunctionRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath functionPath, + @Nonnull final Class resultClass ) + { + super(servicePath, functionPath); + this.resultClass = resultClass; + } + + @Nonnull + @Override + public ResultT execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + final ResultT result = response.as(getResultClass()); + + if( result instanceof VdmEntity ) { + response.getVersionIdentifierFromHeader().peek(((VdmEntity) result)::setVersionIdentifier); + } + return result; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/StructuredProperty.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/StructuredProperty.java new file mode 100644 index 0000000000..74b55e45de --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/StructuredProperty.java @@ -0,0 +1,17 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +/** + * Interface representing a structural property of {@link EntityT} that points towards an object of {@link TargetT}. + * Structured properties are either complex types or navigational properties. + * + * @see com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty + * @see com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty + * + * @param + * Entity this property is part of. + * @param + * {@link VdmObject} this property represents. + */ +interface StructuredProperty, TargetT extends VdmObject> extends Property +{ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java new file mode 100644 index 0000000000..815565abf9 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java @@ -0,0 +1,288 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataSerializationException; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ETagSubmissionStrategy; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odata.client.request.UpdateStrategy; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Representation of an OData request as a fluent interface for further configuring the request and + * {@link #execute(Destination) executing} it. + * + * @param + * The type of the result entity. + */ +@Slf4j +public class UpdateRequestBuilder> + extends + AbstractEntityBasedRequestBuilder, EntityT, ModificationResponse> + implements + ModificationRequestBuilder> +{ + private final Collection includedFields = new HashSet<>(); + + private final Collection excludedFields = new HashSet<>(); + + private UpdateStrategy updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH; + + private ETagSubmissionStrategy eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ETAG_FROM_ENTITY; + + /** + * The entity object to be updated by calling the {@link #execute(Destination)} method. + */ + @Getter( AccessLevel.PROTECTED ) + @Nonnull + private final EntityT entity; + + /** + * Instantiate an {@code UpdateRequestBuilder}. + * + * @param servicePath + * The service path. + * @param entity + * The entity to update. + * @param entityCollection + * The entity collection + */ + public UpdateRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final EntityT entity, + @Nonnull final String entityCollection ) + { + this(servicePath, ODataResourcePath.of(entityCollection, entity.getKey()), entity); + } + + /** + * Instantiate an {@code UpdateRequestBuilder}. + * + * @param servicePath + * The service path. + * @param entityPath + * The {@link ODataResourcePath} that identifies the entity to update. + * @param entity + * The entity to update. + */ + UpdateRequestBuilder( + @Nonnull final String servicePath, + @Nonnull final ODataResourcePath entityPath, + @Nonnull final EntityT entity ) + { + super(servicePath, entityPath); + this.entity = entity; + } + + @SuppressWarnings( "unchecked" ) + @Nonnull + @Override + protected Class getEntityClass() + { + return (Class) getEntity().getClass(); + } + + /** + * Execute the OData update request for the provided entity. + * + * {@inheritDoc} + * + * @return The wrapped service response, exposing response headers, status code and entity references. If the HTTP + * response is not within healthy bounds, then one of the declared runtime exceptions will be thrown with + * further details. + */ + @Nonnull + @Override + public ModificationResponse execute( @Nonnull final Destination destination ) + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final ODataRequestResultGeneric response = toRequest().execute(httpClient); + + return ModificationResponse.of(response, getEntity()); + } + + /** + * Creates an instance of the {@link ODataRequestUpdate} based on the Entity class. + *

+ * The following settings are used to build the Request Builder: + *

    + *
  • the endpoint URL
  • + *
  • the entity collection name
  • + *
  • the key fields of the entity
  • + *
  • the entity JSON payload
  • + *
  • the update strategy (full update or delta)
  • + *
+ * + * @return An initialized {@code ODataRequestUpdate}. + * @throws ODataSerializationException + * If entity cannot be serialized for HTTP request. + */ + @Override + @Nonnull + public ODataRequestUpdate toRequest() + { + final String versionIdentifier = + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()); + final String serializedEntity = getSerializedEntity(); + + final ODataRequestUpdate request = + new ODataRequestUpdate( + getServicePath(), + getResourcePath(), + serializedEntity, + updateStrategy, + versionIdentifier, + ODataProtocol.V4); + + return super.toRequest(request); + } + + /** + * Serialize entity to String depending on update strategy. + * + * @return The serialized String representing the entity. + * @throws ODataSerializationException + * If entity cannot be serialized for HTTP request. + */ + @Nonnull + private String getSerializedEntity() + { + final EntityT entity = getEntity(); + try { + switch( updateStrategy ) { + case REPLACE_WITH_PUT: + return new UpdateRequestHelperPut().toJson(entity, excludedFields); + case MODIFY_WITH_PATCH: + return new UpdateRequestHelperPatch().toJson(entity, includedFields); + default: + throw new IllegalStateException("Unexpected update Strategy: " + updateStrategy); + } + } + catch( final Exception e ) { + final String msg = + String + .format( + "Failed to serialize OData Update HTTP request entity for type %s with strategy %s", + getEntityClass().getSimpleName(), + updateStrategy); + + final ODataRequestUpdate request = + new ODataRequestUpdate( + getServicePath(), + getResourcePath(), + "", + updateStrategy, + eTagSubmissionStrategy.getHeaderFromVersionIdentifier(entity.getVersionIdentifier()), + ODataProtocol.V4); + + throw new ODataSerializationException(request, entity, msg, e); + } + } + + /** + * Allows to explicitly specify entity fields that shall be sent in an update request regardless if the values of + * these fields have been changed. This is helpful in case the API requires to send certain fields in any case in an + * update request. + * + * @param fields + * The fields to be included in the update execution. + * @return The same request builder which will include the specified fields in an update request. + */ + @Nonnull + public final UpdateRequestBuilder includingFields( @Nonnull final FieldReference... fields ) + { + includedFields.addAll(Arrays.asList(fields)); + return this; + } + + /** + * Allows to explicitly specify entity fields that should not be sent in an update request. This is helpful in case + * some services require no read only fields to be sent for update requests. These fields are only excluded in a PUT + * request, they are not considered in a PATCH request. + * + * @param fields + * The fields to be excluded in the update execution. + * @return The same request builder which will exclude the specified fields in an update request. + */ + @Nonnull + public final UpdateRequestBuilder excludingFields( @Nonnull final FieldReference... fields ) + { + Collections.addAll(excludedFields, fields); + return this; + } + + /** + * Allows to control that the request to update the entity is sent with the HTTP method PUT and its payload contains + * all fields of the entity, regardless which of them have been changed. + * + * @return The same request builder which will replace the entity in the remote system + */ + @Nonnull + public final UpdateRequestBuilder replacingEntity() + { + updateStrategy = UpdateStrategy.REPLACE_WITH_PUT; + return this; + } + + /** + * 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. + * + * @return The same request builder which will modify the entity in the remote system. + */ + @Nonnull + public final UpdateRequestBuilder modifyingEntity() + { + updateStrategy = UpdateStrategy.MODIFY_WITH_PATCH; + return this; + } + + /** + * The update request will ignore any version identifier present on the entity and not send an `If-Match` header. + *

+ * Warning: This might lead to a response from the remote system that the `If-Match` header is missing. + *

+ * It depends on the implementation of the remote system whether the `If-Match` header is expected. + * + * @return The same request builder that will not send the `If-Match` header in the update request + */ + @Nonnull + public UpdateRequestBuilder disableVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_NO_ETAG; + return this; + } + + /** + * The update request will ignore any version identifier present on the entity and update the entity, regardless of + * any changes on the remote entity. + *

+ * Warning: Be careful with this option, as this might overwrite any changes made to the remote + * representation of this object. + * + * @return The same request builder that will ignore the version identifier of the entity to update + */ + @Nonnull + public UpdateRequestBuilder matchAnyVersionIdentifier() + { + eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ANY_MATCH_ETAG; + return this; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatch.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatch.java new file mode 100644 index 0000000000..5668db98b6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatch.java @@ -0,0 +1,211 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class UpdateRequestHelperPatch +{ + String toJson( @Nonnull final VdmEntity entity, @Nonnull final Collection includedFields ) + { + return new SerializeHelper(entity, includedFields).toJson(); + } + + @RequiredArgsConstructor + private static class SerializeHelper + { + @Nonnull + private final VdmEntity entity; + + @Nonnull + private final Collection includedFields; + + @Nonnull + private final Gson gson = new GsonBuilder().serializeNulls().create(); + + @Nonnull + private final Map, JsonObject> cache = new IdentityHashMap<>(); + + @Nonnull + private static final TypeAdapterFactory GSON_VDM_ADAPTER_FACTORY = new GsonVdmAdapterFactory(); + + @Nonnull + String toJson() + { + // find field names to be patched + final Set fieldNamesToPatch = new HashSet<>(entity.getChangedFields().keySet()); + includedFields.stream().map(FieldReference::getFieldName).forEach(fieldNamesToPatch::add); + log.debug("The following fields are marked for updates: {}.", fieldNamesToPatch); + + // create the key-value map for entity properties to be patched + final Map patchValues = Maps.filterKeys(entity.toMap(), fieldNamesToPatch::contains); + + // starting helper collection to identify of recursion + final Set> parentObjects = Collections.newSetFromMap(new IdentityHashMap<>()); + parentObjects.add(entity); + + // serialize key-value properties map to GSON object. + final JsonObject o = Objects.requireNonNull((JsonObject) serializeComplexValue(patchValues, parentObjects)); + + if( o.size() < 1 ) { + log + .warn( + """ + Update strategy is to modify with PATCH, but no fields have changed. \ + Make sure to modify the entity via its setters or by naming them explicitly via 'includingFields(fields ...)'. \ + This request may be bound to fail in the target system.\ + """); + } + + // add default OData annotation properties, e.g. @odata.type + entity.getAnnotationProperties().forEach(o::addProperty); + + // translate JsonObject to Json String + return gson.toJson(o); + } + + @Nullable + private + JsonElement + serializeComplexValue( @Nullable final Object input, @Nonnull final Set> parentObjects ) + { + if( input == null ) { + return JsonNull.INSTANCE; + } + + if( input instanceof Map ) { + return ((Map) input) + .entrySet() + .stream() + .collect( + JsonObject::new, + ( m, v ) -> m.add(v.getKey().toString(), serializeComplexValue(v.getValue(), parentObjects)), + ( m1, m2 ) -> m2.keySet().forEach(k2 -> m1.add(k2, m2.get(k2)))); + } + if( input instanceof List ) { + return ((Collection) input) + .stream() + .map(v -> serializeComplexValue(v, parentObjects)) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + } + if( input instanceof VdmObject ) { + final JsonObject cachedValue = cache.get(input); + if( cachedValue != null ) { + return cachedValue; + } + + final JsonObject valueMap = new JsonObject(); + if( !parentObjects.contains(input) ) { + cache.put((VdmObject) input, valueMap); + + // for complex type consider all fields, for entity types filter for changed fields + final Map changedFields; + if( input instanceof VdmComplex ) { + changedFields = ((VdmObject) input).toMapOfFields(); + changedFields.putAll(((VdmObject) input).getCustomFields()); + } else { + changedFields = ((VdmObject) input).getChangedFields(); + } + + // derive helper collection to identify of recursion + final Set> nestedParentObjects = Collections.newSetFromMap(new IdentityHashMap<>()); + nestedParentObjects.addAll(parentObjects); + nestedParentObjects.add((VdmObject) input); + + // serialize changed fields of nested VDM object + final JsonElement serializedMapRaw = serializeComplexValue(changedFields, nestedParentObjects); + final JsonObject serializedMap = Objects.requireNonNull((JsonObject) serializedMapRaw); + serializedMap.keySet().forEach(key -> valueMap.add(key, serializedMap.get(key))); + } + + // if nested VDM object is a VDM entity, handle the key properties + if( input instanceof VdmEntity ) { + final VdmEntity entity = (VdmEntity) input; + + // if no key fields are marked as changed, then add the @id property + if( entity.getKey().getFieldNames().stream().noneMatch(valueMap::has) ) { + valueMap.addProperty("@id", entity.getEntityCollection() + entity.getKey().toEncodedString()); + } + } + + return valueMap; + } + + return serializeSimpleValue(input); + } + + @Nullable + private JsonElement serializeSimpleValue( @Nonnull final T object ) + { + @SuppressWarnings( "unchecked" ) + final TypeToken typeToken = TypeToken.get((Class) object.getClass()); + + final TypeAdapter typeAdapter = GSON_VDM_ADAPTER_FACTORY.create(gson, typeToken); + if( typeAdapter != null ) { + final JsonElement jsonObject = typeAdapter.toJsonTree(object); + log.trace("Simple entity property value {} is serialized to {}.", object, jsonObject); + return jsonObject; + } + + log.debug("GSON type adapter could not be found for entity property value of type {}.", typeToken); + + final JsonPrimitive jsonPrimitive = convertToJsonPrimitive(object, typeToken); + + if( jsonPrimitive == null ) { + log + .warn( + "Could not convert value of type {} to a {} representation.", + typeToken, + JsonElement.class.getSimpleName()); + } + + return jsonPrimitive; + } + + private JsonPrimitive convertToJsonPrimitive( final T value, final TypeToken typeToken ) + { + if( Number.class.isAssignableFrom(typeToken.getRawType()) ) { + final Number numberPrimitive = (Number) value; + return new JsonPrimitive(numberPrimitive); + } + + if( Boolean.class.isAssignableFrom(typeToken.getRawType()) ) { + final Boolean booleanPrimitive = (Boolean) value; + return new JsonPrimitive(booleanPrimitive); + } + + if( Character.class.isAssignableFrom(typeToken.getRawType()) ) { + final Character characterPrimitive = (Character) value; + return new JsonPrimitive(characterPrimitive); + } + + return null; + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPut.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPut.java new file mode 100644 index 0000000000..2ceef1b1d5 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPut.java @@ -0,0 +1,27 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Collection; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; + +class UpdateRequestHelperPut +{ + String toJson( @Nonnull final VdmEntity entity, @Nullable final Collection excludedFields ) + { + final Gson gson = new GsonBuilder().create(); + final JsonObject jsonObject = gson.toJsonTree(entity).getAsJsonObject(); + + // find field names to be removed from PUT request + if( excludedFields != null ) { + excludedFields.stream().map(FieldReference::getFieldName).forEach(jsonObject::remove); + } + + return gson.toJson(jsonObject); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmComplex.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmComplex.java new file mode 100644 index 0000000000..ffd7382fe3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmComplex.java @@ -0,0 +1,12 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +/** + * Complex type in the virtual data model. + * + * @param + * Object type of the complex type. + */ +public abstract class VdmComplex extends VdmObject +{ + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntity.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntity.java new file mode 100644 index 0000000000..5b35788bb2 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntity.java @@ -0,0 +1,88 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.vavr.control.Option; +import lombok.EqualsAndHashCode; + +/** + * Base class for an OData entity. + * + * @param + * The entity type. + */ +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +public abstract class VdmEntity extends VdmObject +{ + @Nullable + private String versionIdentifier = null; + + /** + * Select all properties of an entity. + * + * @param + * The entity type. + * @return A selector for all entity fields. + */ + @Nonnull + protected static SimpleProperty all() + { + return new SimpleProperty.All<>(); + } + + /** + * + * Getter for the version identifier of this entity. + *

+ * This identifier can be used to compare this entity with a remote one. As not the whole entity has to be sent this + * reduces the request overhead. + *

+ * Actual use cases can be checking whether this entity is still current with regards to the remote entity, and + * ensuring that a update/delete operation is done on the expected version of the remote entity. + * + * @return The version identifier. + */ + @Nonnull + public Option getVersionIdentifier() + { + return Option.of(versionIdentifier); + } + + /** + * Setter for the version identifier of this entity. + *

+ * This identifier can be used to compare this entity with a remote one. As not the whole entity has to be sent this + * reduces the request overhead. + *

+ * Actual use cases can be checking whether this entity is still current with regards to the remote entity, and + * ensuring that a update/delete operation is done on the expected version of the remote entity. + * + * @param versionIdentifier + * The version identifier of this entity. + */ + public void setVersionIdentifier( @Nullable final String versionIdentifier ) + { + this.versionIdentifier = versionIdentifier; + } + + /** + * Used by request builders and navigation property methods to construct OData requests. + * + * @return EDMX name of the entity collection identifier. + */ + @Nonnull + protected abstract String getEntityCollection(); + + /** + * Used by request builders and navigation property methods to construct OData requests. + * + * @return Default context path to the OData service. In other words, everything in between the + * {@code protocol://hostname:port} and the OData resource name (entity set, {@code $metadata}, etc.) + */ + @Nullable + protected String getDefaultServicePath() + { + return null; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntitySet.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntitySet.java new file mode 100644 index 0000000000..c3115b150a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntitySet.java @@ -0,0 +1,8 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +/** + * Empty interface to indicate operative support for querying the OData service for the related EntitySet. + */ +public interface VdmEntitySet +{ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityUtil.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityUtil.java new file mode 100644 index 0000000000..4b1f14deb2 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityUtil.java @@ -0,0 +1,38 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.lang.reflect.InvocationTargetException; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException; + +import lombok.RequiredArgsConstructor; + +/** + * Utility class to manage OData entity deserialization. + * + * @param + * Entity type to create new instances from. + */ +@RequiredArgsConstructor +final class VdmEntityUtil> +{ + private final Class entityClass; + + @Nonnull + EntityT newInstance() + { + try { + return entityClass.getDeclaredConstructor().newInstance(); + } + catch( final + NoSuchMethodException + | InvocationTargetException + | InstantiationException + | IllegalAccessException e ) { + throw new ShouldNotHappenException( + "Failed to instantiate object of type " + entityClass.getSimpleName(), + e); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEnum.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEnum.java new file mode 100644 index 0000000000..6775a4bf29 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEnum.java @@ -0,0 +1,60 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Arrays; +import java.util.Objects; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Interface to manage the different properties an Edm.Enum literal contains. + */ +public interface VdmEnum +{ + /** + * Get the name property of the Edm.Enum literal + * + * @return The name property. + */ + @Nonnull + default String getName() + { + return toString(); + } + + /** + * Get the value property of the Edm.Enum literal + * + * @return The value property. + */ + @Nullable + default Long getValue() + { + return null; + } + + /** + * Helper function to resolve enum constant from given type reference and String identifier. + * + * @param enumType + * The enum type reference. + * @param identifier + * The enum constant identifier. + * @param + * The generic enum type. + * @return A + */ + @Nullable + static T getConstant( @Nonnull final Class enumType, @Nullable final String identifier ) + { + final T[] enumConstants = enumType.getEnumConstants(); + if( enumConstants == null ) { + return null; + } + return Arrays + .stream(enumConstants) + .filter(member -> Objects.equals(member.getName(), identifier)) + .findFirst() + .orElse(null); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmObject.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmObject.java new file mode 100644 index 0000000000..7dedb8f7f8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmObject.java @@ -0,0 +1,403 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.collect.Streams; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * Superclass of all entities which contains common elements such as a generic representation of custom fields. + * + * @param + * The type of the implementing object. + */ +@Slf4j +@ToString( doNotUseGetters = true ) +@EqualsAndHashCode( doNotUseGetters = true ) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + creatorVisibility = JsonAutoDetect.Visibility.NONE ) +public abstract class VdmObject +{ + /** + * The OData V4 JSON key to access an entity EDM type definition. + */ + public static final String[] ODATA_TYPE_ANNOTATIONS = { "@odata.type", "@type" }; + + /** + * The OData V4 JSON key to access an entity version identifier. + */ + public static final String[] ODATA_VERSION_ANNOTATIONS = { "@odata.etag", "@etag" }; + + @JsonIgnore + @Nonnull + private final transient Map customFields = new LinkedHashMap<>(); + + /** + * A mapping of the OData field name to the original value. + *

+ * This should be updated via {@link #rememberChangedField(String, Object)} on every set call of a property. + */ + @JsonIgnore + @Nonnull + protected final transient Map changedOriginalFields = new HashMap<>(); + + /** + * Returns the names of the custom fields of this object. + * + * @return The names of the custom fields of this object. + */ + @Nonnull + public Set getCustomFieldNames() + { + return customFields.keySet(); + } + + /** + * Returns the names and values of a custom field. + * + * @return All the names & values of custom fields as a map. + */ + @JsonAnyGetter + @Nonnull + public Map getCustomFields() + { + return customFields; + } + + /** + * Sets the value of a single custom field. + * + * @param customFieldName + * Name of the custom field. + * @param value + * Value of the custom field. + */ + @JsonAnySetter + public void setCustomField( @Nonnull final String customFieldName, @Nullable final Object value ) + { + rememberChangedField(customFieldName, customFields.get(customFieldName)); + customFields.put(customFieldName, value); + } + + /** + * Sets the value of a single custom field. If the EntityField passed as parameter holds a TypeConverter, the value + * will be converted before it's stored. + * + * @param customField + * Name of the custom field, represented as an EntityField object. + * @param value + * Value of the custom field. + * @param + * The type of the custom field to set. + */ + public < + FieldT> void setCustomField( @Nonnull final SimpleProperty customField, @Nullable final FieldT value ) + { + setCustomField(customField.getFieldName(), value); + } + + /** + * Checks whether this object contains a custom field with the given name. + * + * @param customFieldName + * Name of the custom field to check for + * + * @return {@code true} if this entity has a custom field with the given name, {@code false} otherwise. + */ + public boolean hasCustomField( @Nonnull final String customFieldName ) + { + return customFields.containsKey(customFieldName); + } + + /** + * Checks whether this object contains a value for the given custom field. + * + * @param customField + * Custom field to check for, represented as an {@code EntityField} object. + * + * @return {@code true} if this object has a custom field with the name of the given field, {@code false} otherwise. + */ + public boolean hasCustomField( @Nonnull final SimpleProperty customField ) + { + return hasCustomField(customField.getFieldName()); + } + + /** + * This method allows for retrieval of custom fields that are added to the underlying OData services. + * + * @param customFieldName + * Name of the field returned by the underlying OData service. + * @param + * The type of the returned field. + * + * @return The value of the custom field. Actual type will depend on the type configured in the underlying OData + * service. + * + * @throws NoSuchElementException + * if no field with the given name could be found. + */ + @SuppressWarnings( "unchecked" ) + @Nullable + public FieldT getCustomField( @Nonnull final String customFieldName ) + throws NoSuchElementException + { + if( !hasCustomField(customFieldName) ) { + final String msg = "Object has no field with name '" + customFieldName + "'."; + log.debug(msg); + throw new NoSuchElementException(msg); + } + return (FieldT) customFields.get(customFieldName); + } + + /** + * This method allows for retrieval of custom fields that are added to the underlying OData services. If the + * EntityField passed as parameter holds a TypeConverter, the value will be converted before it's returned. + * + * @param customField + * Field returned by the underlying OData service. + * @param + * The type of the returned field. + * + * @return The value of the custom field. Actual type will depend on the type configured in the underlying OData + * service. + * + * @throws NoSuchElementException + * if no field with the given name could be found. + */ + @Nullable + public FieldT getCustomField( @Nonnull final SimpleProperty customField ) + throws NoSuchElementException + { + return getCustomField(customField.getFieldName()); + } + + /** + * Returns the annotation properties. + * + * @return List of OData annotation properties. + */ + @Nonnull + public Map getAnnotationProperties() + { + final Map properties = new HashMap<>(); + properties.put(ODATA_TYPE_ANNOTATIONS[0], "#" + getOdataType()); + + return properties; + } + + /** + * Returns the EDMX type of this entity. + * + * @return The EDMX type of this entity. + */ + @Nonnull + public abstract String getOdataType(); + + /** + * Returns the class of this object. + * + * @return The class of this object. + */ + @Nonnull + public abstract Class getType(); + + /** + * Returns the compound key of this object. + * + * @return The compound key of this object. + */ + @Nonnull + protected ODataEntityKey getKey() + { + return new ODataEntityKey(ODataProtocol.V4); + } + + /** + * Read entity data from generic map. + * + * @param values + * The key-value map. + */ + protected void fromMap( final Map values ) + { + for( final Map.Entry entry : values.entrySet() ) { + setCustomField(entry.getKey(), entry.getValue()); + } + + resetChangedFields(); + } + + /** + * Get the custom fields as value map. + * + * @return The custom fields. + */ + @Nonnull + protected Map toMapOfCustomFields() + { + return new HashMap<>(getCustomFields()); + } + + /** + * Get the custom field names. + * + * @return The custom field names. + */ + @Nonnull + protected Set getSetOfCustomFields() + { + return new HashSet<>(getCustomFields().keySet()); + } + + /** + * Get all fields as map. + * + * @return The fields as map. + */ + @Nonnull + protected Map toMapOfFields() + { + return new HashMap<>(); + } + + /** + * Get the field names. + * + * @return The field names. + */ + @Nonnull + protected Set getSetOfFields() + { + return Sets.newHashSet(toMapOfFields().keySet()); + } + + /** + * Get navigation properties as map. + * + * @return The navigation properties. + */ + @Nonnull + protected Map toMapOfNavigationProperties() + { + return new HashMap<>(); + } + + /** + * Get navigation property names as set. + * + * @return The navigation property names. + */ + @Nonnull + protected Set getSetOfNavigationProperties() + { + return Sets.newHashSet(toMapOfNavigationProperties().keySet()); + } + + /** + * Translate the entity data to key-value map. + * + * @return The map representation of the entity. + */ + @Nonnull + protected Map toMap() + { + final Map values = new HashMap<>(); + + values.putAll(toMapOfFields()); + values.putAll(toMapOfNavigationProperties()); + values.putAll(toMapOfCustomFields()); + + return values; + } + + /** + * Returns map of all fields which have been changed on this entity along with their updated values. + * + * @return Map containing all changed fields with their current value. + */ + @Nonnull + public Map getChangedFields() + { + final Map currentFields = new HashMap<>(); + currentFields.putAll(toMapOfFields()); + currentFields.putAll(getCustomFields()); + + return Maps.filterEntries(currentFields, f -> f != null && isFieldChanged(f.getKey(), f.getValue())); + } + + private boolean isFieldChanged( @Nonnull final String fieldName, @Nullable final Object currentValue ) + { + final Object overriddenValue = changedOriginalFields.get(fieldName); + if( currentValue == null && overriddenValue == null ) { + return false; + } + + if( changedOriginalFields.containsKey(fieldName) && !Objects.equals(currentValue, overriddenValue) ) { + return true; + } + + // property was either not updated directly, or the values are still "equal" (according to Objects.equals) + if( currentValue instanceof VdmObject ) { + return !((VdmObject) currentValue).getChangedFields().isEmpty(); + } + + if( currentValue instanceof Iterable ) { + return Streams + .stream((Iterable) currentValue) + .filter(VdmObject.class::isInstance) + .anyMatch(obj -> !((VdmObject) obj).getChangedFields().isEmpty()); + } + + return false; + } + + /** + * Remembers the original value of a changed field. + * + * @param fieldName + * The name of the field that is changed. + * @param valueBeforeChange + * The original value before the change. + */ + protected void rememberChangedField( @Nonnull final String fieldName, @Nullable final Object valueBeforeChange ) + { + if( !changedOriginalFields.containsKey(fieldName) ) { + changedOriginalFields.put(fieldName, valueBeforeChange); + } + } + + /** + * Resets the map of all fields which have been changed on this entity. + *

+ * After calling this method, no field is considered changed, until you change the value of fields on this entity + * afterwards. + */ + public void resetChangedFields() + { + changedOriginalFields.clear(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/EntityReference.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/EntityReference.java new file mode 100644 index 0000000000..0eb3d6531e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/EntityReference.java @@ -0,0 +1,20 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +/** + * Generic interface to provide the original entity class reference. + * + * @param + * Type of the entity which references the value. + */ +public interface EntityReference +{ + /** + * Get the type of the entity which references the value. + * + * @return The entity type, + */ + @Nonnull + Class getEntityType(); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FieldOrdering.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FieldOrdering.java new file mode 100644 index 0000000000..18af8e7602 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FieldOrdering.java @@ -0,0 +1,80 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.OrderExpression; +import com.sap.cloud.sdk.datamodel.odata.client.query.Order; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Represents an asc/desc ordering of an entity via a property. + * + * @param + * The entity type that is ordered. + */ +@Getter +@RequiredArgsConstructor +public class FieldOrdering +{ + private final SimpleProperty property; + private final Order order; + + /** + * Creates a {@code FieldOrdering} representing an ascending order over the given property + * + * @param + * The entity the ordering is applied to. + * @param property + * The property of {@code EntityT} to be sorted in descending order. + * @return The FieldOrdering. + */ + @Nonnull + public static FieldOrdering asc( @Nonnull final SimpleProperty property ) + { + return new FieldOrdering<>(property, Order.ASC); + } + + /** + * Creates a {@code FieldOrdering} representing an descending order over the given property. + * + * @param + * The entity the ordering is applied to. + * @param property + * The property of {@code EntityT} to be sorted in descending order. + * @return The FieldOrdering. + */ + @Nonnull + public static FieldOrdering desc( @Nonnull final SimpleProperty property ) + { + return new FieldOrdering<>(property, Order.DESC); + } + + /** + * Builds an {@link OrderExpression} out of individual field orderings. The expression represents a sorting where + * the orderings are applied in the order they are given. + * + * @param orderings + * The {@code FieldOrdering}s that should be applied to achieve a sorting. + * @return The resulting {@link OrderExpression} or {@code null}, if no orderings where given. + */ + @Nullable + public static OrderExpression toOrderExpression( @Nonnull final FieldOrdering... orderings ) + { + final Queue> fieldOrderings = new LinkedList<>(Arrays.asList(orderings)); + if( fieldOrderings.isEmpty() ) { + return null; + } + final FieldOrdering first = fieldOrderings.poll(); + final OrderExpression expr = OrderExpression.of(first.getProperty().getFieldName(), first.getOrder()); + fieldOrderings.forEach(o -> expr.and(o.getProperty().getFieldName(), o.getOrder())); + return expr; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableBoolean.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableBoolean.java new file mode 100644 index 0000000000..f237367500 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableBoolean.java @@ -0,0 +1,132 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionLogical; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Boolean. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableBoolean extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableBoolean, FilterExpression + { + @Delegate + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /*** + * Creates a new Expression from a custom filter expression. Allows for untyped expressions to be supplied to the + * VDM. + * + * @param delegateExpression + * The expression to which the Expression delegates. + * @param entityType + * The expected entity type. + * @param + * Type of the entity which references the value. + * @return A new Expression. + * + * @since 4.8.0 + */ + @Nonnull + static FilterableBoolean fromCustomFilter( + @Nonnull final ValueBoolean delegateExpression, + @Nonnull final Class entityType ) + { + return new Expression<>((FilterExpression) delegateExpression, entityType); + } + + /** + * Combine current filter expression with another expression in conjunction. + * + * @param operand + * The other expression. + * @return This FluentHelper reference. + */ + @Nonnull + default FilterableBoolean and( @Nonnull final FilterableBoolean operand ) + { + final ValueBoolean value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionLogical.and(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * Combine the filter expression with a boolean value in conjunction. + * + * @param operand + * A boolean value. + * @return This FluentHelper reference. + */ + @Nonnull + default FilterableBoolean and( @Nonnull final Boolean operand ) + { + final ValueBoolean value = (ValueBoolean) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.and(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * Combine the filter expression with another expression in disjunction. + * + * @param operand + * The other expression. + * @return This FluentHelper reference. + */ + @Nonnull + default FilterableBoolean or( @Nonnull final FilterableBoolean operand ) + { + final ValueBoolean value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionLogical.or(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * Combine the filter expression with a boolean value in disjunction. + * + * @param operand + * The other expression. + * @return This FluentHelper reference. + */ + @Nonnull + default FilterableBoolean or( @Nonnull final Boolean operand ) + { + final ValueBoolean value = (ValueBoolean) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.or(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * Negate the filter expression. + * + * @return This FluentHelper reference. + */ + @Nonnull + default FilterableBoolean not() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.not(this::getExpression); + return new Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableCollection.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableCollection.java new file mode 100644 index 0000000000..37b57b23e7 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableCollection.java @@ -0,0 +1,343 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.util.function.Predicate; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionCollection; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueCollection; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Collection. + * + * @param + * Type of the entity which references the value. + * @param + * Type of the item type the collection holds. + */ +public interface FilterableCollection extends Expressions.OperandMultiple, EntityReference +{ + /** + * Get the item type the collection holds. + * + * @return The item type. + */ + @Nonnull + Class getItemType(); + + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + * @param + * Type of the item type the collection holds. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableCollection + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + + @Nonnull + private final Class itemType; + } + + /** + * Filter by expression "hasSubset". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean hasSubset( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.hasSubset(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "hasSubset". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean hasSubset( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.hasSubset(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "hasSubSequence". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean hasSubSequence( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.hasSubSequence(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "hasSubSequence". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean hasSubSequence( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.hasSubSequence(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "contains". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean contains( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.contains(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "contains". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean contains( @Nonnull final Iterable operand ) + { + final Expressions.OperandMultiple value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.contains(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "startsWith". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean startsWith( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.startsWith(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "startsWith". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean startsWith( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.startsWith(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "endsWith". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean endsWith( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.endsWith(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "endsWith". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean endsWith( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.endsWith(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "indexOf". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger indexOf( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.indexOf(this, operand); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "indexOf". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger indexOf( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.indexOf(this, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "concat". + * + * @param operand + * Only operand of collection type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableCollection concat( @Nonnull final FilterableCollection operand ) + { + final FilterExpression expression = FilterExpressionCollection.concat(this, operand); + return new Expression<>(expression, getEntityType(), getItemType()); + } + + /** + * Filter by expression "concat". + * + * @param operand + * Only operand of Java iterable. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableCollection concat( @Nonnull final Iterable operand ) + { + final ValueCollection value = ValueCollection.literal(operand); + final FilterExpression expression = FilterExpressionCollection.concat(this, value); + return new Expression<>(expression, getEntityType(), getItemType()); + } + + /** + * Filter by expression "length". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger length() + { + final FilterExpression expression = FilterExpressionCollection.length(this); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "substring". + * + * @param operand + * Only operand of Integer type. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableCollection substring( @Nonnull final Integer operand ) + { + final ValueNumeric value = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionCollection.substring(this, value); + return new Expression<>(expression, getEntityType(), getItemType()); + } + + /** + * Filter by expression "substring". + * + * @param operandIndex + * Operand of Integer type to mark the start of the subset. + * @param operandLength + * Operand of Integer type to mark the size of the subset. + * @return The FluentHelper filter. + */ + @Nonnull + default + FilterableCollection + substring( @Nonnull final Integer operandIndex, @Nonnull final Integer operandLength ) + { + final ValueNumeric value1 = ValueNumeric.literal(operandIndex); + final ValueNumeric value2 = ValueNumeric.literal(operandLength); + final ValueCollection.Expression expression = FilterExpressionCollection.substring(this, value1, value2); + return new Expression<>(expression, getEntityType(), getItemType()); + } + + /** + * Filter by lambda expression "all". + * + * @param operand + * Operand to provide a generic filter to the collection item. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean all( @Nonnull final FilterableBoolean operand ) + { + final Predicate lambdaFieldPredicate = + o -> o instanceof EntityReference && ((EntityReference) o).getEntityType().equals(getItemType()); + final ValueBoolean.Expression expression = + FilterExpressionCollection.all(this, operand::getExpression, lambdaFieldPredicate); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by lambda expression "any". + * + * @param operand + * Operand to provide a generic filter to the collection item. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean any( @Nonnull final FilterableBoolean operand ) + { + final Predicate lambdaFieldPredicate = + o -> o instanceof EntityReference && ((EntityReference) o).getEntityType().equals(getItemType()); + final FilterExpression expression = + FilterExpressionCollection.any(this, operand::getExpression, lambdaFieldPredicate); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableComplex.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableComplex.java new file mode 100644 index 0000000000..427b8fc1f4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableComplex.java @@ -0,0 +1,94 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionLogical; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueEnum; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by an OData complex property. + * + * @param + * Type of the entity which references the value. + * @param + * Type of the complex property. + */ +public interface FilterableComplex extends Expressions.OperandSingle, EntityReference +{ + /** + * Filter by expression "has". + * + * @param operand + * A generic String to be applied to the expression + * @return The FluentHelper filter + */ + @Nonnull + default FilterableBoolean has( @Nonnull final String operand ) + { + final ValueEnum value = ValueEnum.literal(operand); + final FilterExpression expression = FilterExpressionLogical.has(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "has". + * + * @param operand + * A generic String to be applied to the expression + * @param + * The enum value type. + * @return The FluentHelper filter + */ + @Nonnull + default < + EnumT extends VdmEnum> FilterableBoolean has( @Nonnull final FilterableEnum operand ) + { + final FilterExpression expression = FilterExpressionLogical.has(this, operand::getExpression); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "in". + * + * @param operand + * The generic operands to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean in( @Nonnull final FilterableCollection operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.in(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "eq null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.equalTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.notEqualTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDate.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDate.java new file mode 100644 index 0000000000..641cb59644 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDate.java @@ -0,0 +1,192 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.time.Duration; +import java.time.LocalDate; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionTemporal; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueDate; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueDuration; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Date. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableDate extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableDate, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /** + * + * Filter by expression "day". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger dateDay() + { + final ValueDate thisDate = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.day(thisDate); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "month". + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableNumericInteger dateMonth() + { + final ValueDate thisDate = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.month(thisDate); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "year". + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableNumericInteger dateYear() + { + final ValueDate thisDate = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.year(thisDate); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to add to the date expression. + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDate add( @Nonnull final FilterableDuration operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.add(thisDate, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to ad to the date expression. + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDate add( @Nonnull final Duration operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisDate, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The duration to subtract from the date. + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDate subtract( @Nonnull final FilterableDuration operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDate, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The duration to subtract from the date. + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDate subtract( @Nonnull final Duration operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDate, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The other date to calculate the difference from. + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDuration difference( @Nonnull final FilterableDate operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDate, value); + return new FilterableDuration.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The other date to calculate the difference from. + * + * @return The FluentHelper filter + */ + @Nonnull + default FilterableDuration difference( @Nonnull final LocalDate operand ) + { + final ValueDate thisDate = this::getExpression; + final ValueDate value = ValueDate.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDate, value); + return new FilterableDuration.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDateTime.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDateTime.java new file mode 100644 index 0000000000..ed6990ba42 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDateTime.java @@ -0,0 +1,255 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.time.Duration; +import java.time.OffsetDateTime; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionTemporal; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueDateTimeOffset; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueDuration; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by DateTime. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableDateTime extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableDateTime, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /** + * + * Filter by expression "date". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDate date() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.date(thisDateTime); + return new FilterableDate.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "fractionalseconds". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal timeFractionalSeconds() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.fractionalSeconds(thisDateTime); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "second". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeSecond() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.second(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "minute". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeMinute() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.minute(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "hour". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeHour() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.hour(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "day". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger dateDay() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.day(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "month". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger dateMonth() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.month(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "year". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger dateYear() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.year(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "time". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableTime time() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.time(thisDateTime); + return new FilterableTime.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "offsetminutes". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger offsetMinutes() + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.totalOffsetMinutes(thisDateTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to add to the date time. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDateTime add( @Nonnull final FilterableDuration operand ) + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.add(thisDateTime, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to add to the date time. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDateTime add( @Nonnull final Duration operand ) + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisDateTime, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The duration to subtract from the date time. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDateTime subtract( @Nonnull final FilterableDuration operand ) + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDateTime, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to subtract from the date time. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDateTime subtract( @Nonnull final Duration operand ) + { + final ValueDateTimeOffset thisDateTime = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDateTime, value); + return new Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDuration.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDuration.java new file mode 100644 index 0000000000..626d26eef3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableDuration.java @@ -0,0 +1,214 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.time.Duration; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionTemporal; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueDuration; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Duration. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableDuration extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableDuration, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /** + * + * Filter by expression "offsetseconds". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger offsetSeconds() + { + final ValueDuration thisDuration = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.totalOffsetSeconds(thisDuration); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to add to the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration add( @Nonnull final FilterableDuration operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.add(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "add". + * + * @param operand + * The duration to add to the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration add( @Nonnull final Duration operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The duration to subtract from this duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration subtract( @Nonnull final FilterableDuration operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueDuration value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The duration to subtract from this duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration subtract( @Nonnull final Duration operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueDuration value = ValueDuration.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "mul". + * + * @param operand + * The product to be used to multiply the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration multiply( @Nonnull final FilterableNumeric operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "mul". + * + * @param operand + * The product to be used to multiply the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration multiply( @Nonnull final Number operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueNumeric value = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "div". + * + * @param operand + * The quotient to be used to divide the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration divide( @Nonnull final FilterableNumeric operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.divide(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "div". + * + * @param operand + * The quotient to be used to divide the duration. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration divide( @Nonnull final Number operand ) + { + final ValueDuration thisDuration = this::getExpression; + final ValueNumeric value = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.divide(thisDuration, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "-". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableDuration negate() + { + final ValueDuration thisDuration = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.negate(thisDuration); + return new Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableEnum.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableEnum.java new file mode 100644 index 0000000000..f8d1b274ab --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableEnum.java @@ -0,0 +1,135 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionLogical; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueEnum; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Enum. + * + * @param + * Type of the entity which references the value. + * @param + * Type of the Enum value. + */ +public interface FilterableEnum extends Expressions.Operand, EntityReference +{ + /** + * OData Enum type identifier. + * + * @return The enum type identifier. + */ + @Nonnull + String getEnumType(); + + /** + * + * Filter by expression "eq". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalTo( @Nonnull final FilterableEnum operand ) + { + final FilterExpression expression = FilterExpressionLogical.equalTo(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "eq". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalTo( @Nullable final EnumT operand ) + { + final Expressions.Operand value = + operand == null ? Expressions.Operand.NULL : ValueEnum.literal(getEnumType(), operand.getName()); + final FilterExpression expression = FilterExpressionLogical.equalTo(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualTo( @Nonnull final FilterableEnum operand ) + { + final FilterExpression expression = FilterExpressionLogical.notEqualTo(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualTo( @Nullable final EnumT operand ) + { + final Expressions.Operand value = + operand == null ? Expressions.Operand.NULL : ValueEnum.literal(getEnumType(), operand.getName()); + final FilterExpression expression = FilterExpressionLogical.notEqualTo(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "eq null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.equalTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.notEqualTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "in". + * + * @param operand + * The generic operands to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean in( @Nonnull final FilterableCollection operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.in(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableGuid.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableGuid.java new file mode 100644 index 0000000000..03f3122da0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableGuid.java @@ -0,0 +1,14 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.util.UUID; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Guid. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableGuid extends FilterableValue +{ + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumeric.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumeric.java new file mode 100644 index 0000000000..cdfe021737 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumeric.java @@ -0,0 +1,340 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Number. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableNumeric extends FilterableValue +{ + /** + * + * Filter by expression "add". + * + * @param operand + * The number to add. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric add( @Nonnull final Long operand ); + + /** + * + * Filter by expression "add". + * + * @param operand + * The number to add. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric add( @Nonnull final Integer operand ); + + /** + * + * Filter by expression "add". + * + * @param operand + * The number to add. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumericDecimal add( @Nonnull final Number operand ); + + /** + * + * Filter by expression "add". + * + * @param operand + * The number to add. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric add( @Nonnull final FilterableNumericInteger operand ); + + /** + * + * Filter by expression "add". + * + * @param operand + * The number to add. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal add( @Nonnull final FilterableNumericDecimal operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.add(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "mul". + * + * @param operand + * The number to multiply. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric multiply( @Nonnull final Long operand ); + + /** + * + * Filter by expression "mul". + * + * @param operand + * The number to multiply. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric multiply( @Nonnull final Integer operand ); + + /** + * + * Filter by expression "mul". + * + * @param operand + * The number to multiply. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumericDecimal multiply( @Nonnull final Number operand ); + + /** + * + * Filter by expression "mul". + * + * @param operand + * The number to multiply. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric multiply( @Nonnull final FilterableNumericInteger operand ); + + /** + * + * Filter by expression "mul". + * + * @param operand + * The number to multiply. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal multiply( @Nonnull final FilterableNumericDecimal operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.multiply(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "sub". + * + * @param operand + * The number to subtract. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric subtract( @Nonnull final Long operand ); + + /** + * + * Filter by expression "sub". + * + * @param operand + * The number to subtract. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric subtract( @Nonnull final Integer operand ); + + /** + * + * Filter by expression "sub". + * + * @param operand + * The number to subtract. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumericDecimal subtract( @Nonnull final Number operand ); + + /** + * + * Filter by expression "sub". + * + * @param operand + * The number to subtract. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric subtract( @Nonnull final FilterableNumericInteger operand ); + + /** + * + * Filter by expression "sub". + * + * @param operand + * The number to subtract. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal subtract( @Nonnull final FilterableNumericDecimal operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.subtract(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "div". + * + * @param operand + * The number to divide. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal divide( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.divide(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * Filter by expression "div". + * + * @param operand + * The number to divide. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal divide( @Nonnull final FilterableNumericDecimal operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.divide(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "div". + * + * @param operand + * The number to divide. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal divide( @Nonnull final Number operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = ValueNumeric.literal(operand); + final ValueNumeric.Expression expression = FilterExpressionArithmetic.divide(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "mod". + * + * @param operand + * The base number to calculate the modulo from. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric modulo( @Nonnull final Long operand ); + + /** + * + * Filter by expression "mod". + * + * @param operand + * The base number to calculate the modulo from. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric modulo( @Nonnull final Integer operand ); + + /** + * + * Filter by expression "mod". + * + * @param operand + * The base number to calculate the modulo from. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumericDecimal modulo( @Nonnull final Number operand ); + + /** + * + * Filter by expression "mod". + * + * @param operand + * The base number to calculate the modulo from. + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric modulo( @Nonnull final FilterableNumericInteger operand ); + + /** + * + * Filter by expression "mod". + * + * @param operand + * The base number to calculate the modulo from. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal modulo( @Nonnull final FilterableNumericDecimal operand ) + { + final ValueNumeric thisNumeric = this::getExpression; + final ValueNumeric value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionArithmetic.modulo(thisNumeric, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "-". + * + * @return The FluentHelper filter. + */ + @Nonnull + FilterableNumeric negate(); +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericDecimal.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericDecimal.java new file mode 100644 index 0000000000..7b1761008e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericDecimal.java @@ -0,0 +1,249 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Integer. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableNumericDecimal extends FilterableNumeric +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableNumericDecimal, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + @Override + @Nonnull + default FilterableNumericDecimal add( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal add( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal add( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal add( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal multiply( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal multiply( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal multiply( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal multiply( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal subtract( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, value); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal subtract( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal subtract( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal subtract( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal modulo( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, value); + return new Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal modulo( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal modulo( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal modulo( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericDecimal negate() + { + final ValueNumeric thisNumber = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.negate(thisNumber); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ceiling". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger ceil() + { + final ValueNumeric thisNumber = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.ceiling(thisNumber); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "floor". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger floor() + { + final ValueNumeric thisNumber = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.floor(thisNumber); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "round". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger round() + { + final ValueNumeric thisNumber = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.round(thisNumber); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericInteger.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericInteger.java new file mode 100644 index 0000000000..344b171c82 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableNumericInteger.java @@ -0,0 +1,207 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionArithmetic; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Integer. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableNumericInteger extends FilterableNumeric +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableNumericInteger, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + @Override + @Nonnull + default FilterableNumericInteger add( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal add( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger add( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger add( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.add(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger multiply( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal multiply( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger multiply( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger multiply( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.multiply(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger subtract( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal subtract( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger subtract( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger subtract( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.subtract(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger modulo( @Nonnull final FilterableNumericInteger operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric value = operand::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, value); + return new Expression<>(expression, getEntityType()); + } + + @Nonnull + @Override + default FilterableNumericDecimal modulo( @Nonnull final Number operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger modulo( @Nonnull final Long operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger modulo( @Nonnull final Integer operand ) + { + final ValueNumeric thisNumber = this::getExpression; + final ValueNumeric literal = ValueNumeric.literal(operand); + final FilterExpression expression = FilterExpressionArithmetic.modulo(thisNumber, literal); + return new Expression<>(expression, getEntityType()); + } + + @Override + @Nonnull + default FilterableNumericInteger negate() + { + final ValueNumeric thisNumber = this::getExpression; + final FilterExpression expression = FilterExpressionArithmetic.negate(thisNumber); + return new Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableString.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableString.java new file mode 100644 index 0000000000..f87b5200b4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableString.java @@ -0,0 +1,336 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionString; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueNumeric; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueString; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by String. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableString extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableString, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /** + * + * Filter by expression "matchesPattern". + * + * @param operand + * String expression to match the string against. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean matches( @Nonnull final String operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionString.matchesPattern(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "matchesPattern". + * + * @param operand + * String expression to match the string against. + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean matches( @Nonnull final FilterableString operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionString.matchesPattern(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "tolower". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString toLower() + { + final ValueString.Expression expression = FilterExpressionString.toLower(this::getExpression); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "toupper". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString toUpper() + { + final ValueString.Expression expression = FilterExpressionString.toUpper(this::getExpression); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "trim". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString trim() + { + final ValueString.Expression expression = FilterExpressionString.trim(this::getExpression); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "length". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger length() + { + final ValueNumeric.Expression expression = FilterExpressionString.length(this::getExpression); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "concat". + * + * @param operand + * The string to concatenate with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString concat( @Nonnull final FilterableString operand ) + { + final ValueString value = operand::getExpression; + final ValueString.Expression expression = FilterExpressionString.concat(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "concat". + * + * @param operand + * The string to concatenate with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString concat( @Nonnull final String operand ) + { + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueString.Expression expression = FilterExpressionString.concat(this::getExpression, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "startswith". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean startsWith( @Nonnull final FilterableString operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionString.startsWith(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "startswith". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean startsWith( @Nonnull final String operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionString.startsWith(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "endswith". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean endsWith( @Nonnull final FilterableString operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionString.endsWith(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "endswith". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean endsWith( @Nonnull final String operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionString.endsWith(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "contain". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean contains( @Nonnull final FilterableString operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = operand::getExpression; + final ValueBoolean.Expression expression = FilterExpressionString.contains(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "contain". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean contains( @Nonnull final String operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionString.contains(thisString, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "indexof". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger indexOf( @Nonnull final FilterableString operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = operand::getExpression; + final ValueNumeric.Expression expression = FilterExpressionString.indexOf(thisString, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "indexof". + * + * @param operand + * The substring which is checked for. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger indexOf( @Nonnull final String operand ) + { + final ValueString thisString = this::getExpression; + final ValueString value = (ValueString) Expressions.createOperand(operand); + final ValueNumeric.Expression expression = FilterExpressionString.indexOf(thisString, value); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "substring". + * + * @param operand + * The number of characters to cut off. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableString substring( @Nonnull final Integer operand ) + { + final ValueString thisString = this::getExpression; + final ValueNumeric value = (ValueNumeric) Expressions.createOperand(operand); + final ValueString.Expression expression = FilterExpressionString.substring(thisString, value); + return new Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "substring". + * + * @param operandIndex + * The number of characters to cut off. + * @param operandLength + * The number of characters to keep in. + * @return The FluentHelper filter. + */ + @Nonnull + default + FilterableString + substring( @Nonnull final Integer operandIndex, @Nonnull final Integer operandLength ) + { + final ValueString thisString = this::getExpression; + final ValueNumeric index = (ValueNumeric) Expressions.createOperand(operandIndex); + final ValueNumeric length = (ValueNumeric) Expressions.createOperand(operandLength); + final ValueString.Expression expression = FilterExpressionString.substring(thisString, index, length); + return new Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableTime.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableTime.java new file mode 100644 index 0000000000..57fc39c9ae --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableTime.java @@ -0,0 +1,96 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.time.LocalTime; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionTemporal; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueTimeOfDay; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by Time. + * + * @param + * Type of the entity which references the value. + */ +public interface FilterableTime extends FilterableValue +{ + /** + * Wrapper expression class, which delegates to another operation. + * + * @param + * Type of the entity which references the value. + */ + @RequiredArgsConstructor + @Getter + class Expression implements FilterableTime, FilterExpression + { + @Delegate + @Nonnull + private final FilterExpression delegate; + + @Nonnull + private final Class entityType; + } + + /** + * + * Filter by expression "fractionalseconds". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericDecimal timeFractionalSeconds() + { + final ValueTimeOfDay thisTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.fractionalSeconds(thisTime); + return new FilterableNumericDecimal.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "second". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeSecond() + { + final ValueTimeOfDay thisTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.second(thisTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "minute". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeMinute() + { + final ValueTimeOfDay thisTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.minute(thisTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "hour". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableNumericInteger timeHour() + { + final ValueTimeOfDay thisTime = this::getExpression; + final FilterExpression expression = FilterExpressionTemporal.hour(thisTime); + return new FilterableNumericInteger.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableValue.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableValue.java new file mode 100644 index 0000000000..b28bf441ce --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableValue.java @@ -0,0 +1,284 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import java.io.Serializable; +import java.util.Arrays; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.Expressions; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpressionLogical; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; + +/** + * Fluent helper class to provide filter functions to OData expressions referenced by all value types. + * + * @param + * Type of the entity which references the value. + * @param + * Type of the value the filterable field holds. + */ +public interface FilterableValue + extends + Expressions.Operand, + EntityReference +{ + /** + * + * Filter by expression "eq null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.equalTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne null". + * + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualToNull() + { + final ValueBoolean.Expression expression = FilterExpressionLogical.notEqualTo(this, Expressions.Operand.NULL); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "eq". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean equalTo( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.equalTo(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "eq". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean equalTo( @Nullable final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.equalTo(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean notEqualTo( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.notEqualTo(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ne". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean notEqualTo( @Nullable final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.notEqualTo(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "lt". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean lessThan( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.lessThan(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "lt". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean lessThan( @Nonnull final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.lessThan(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "le". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean lessThanEqual( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.lessThanEquals(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "le". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean lessThanEqual( @Nonnull final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.lessThanEquals(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "gt". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean greaterThan( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.greaterThan(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "gt". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean greaterThan( @Nonnull final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.greaterThan(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ge". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean greaterThanEqual( @Nonnull final FilterableValue operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.greaterThanEquals(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "ge". + * + * @param operand + * The generic operand to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for the provided Java literal. + */ + @Nonnull + default FilterableBoolean greaterThanEqual( @Nonnull final PrimitiveT operand ) + { + final Expressions.Operand value = Expressions.createOperand(operand); + final ValueBoolean.Expression expression = FilterExpressionLogical.greaterThanEquals(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "in". + * + * @param operands + * The generic operands to compare with. + * @return The FluentHelper filter. + * @throws IllegalArgumentException + * When there is no mapping found for any of the provided Java literals. + */ + @SuppressWarnings( "unchecked" ) + @Nonnull + default FilterableBoolean in( @Nonnull final PrimitiveT... operands ) + { + final Expressions.Operand[] value = + Arrays.stream(operands).map(Expressions::createOperand).toArray(Expressions.Operand[]::new); + final ValueBoolean.Expression expression = FilterExpressionLogical.in(this, value); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } + + /** + * + * Filter by expression "in". + * + * @param operand + * The generic operands to compare with. + * @return The FluentHelper filter. + */ + @Nonnull + default FilterableBoolean in( @Nonnull final FilterableCollection operand ) + { + final ValueBoolean.Expression expression = FilterExpressionLogical.in(this, operand); + return new FilterableBoolean.Expression<>(expression, getEntityType()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/TestUtility.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/TestUtility.java new file mode 100644 index 0000000000..352e18c032 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/TestUtility.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.datamodel.odatav4; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import com.google.common.io.Resources; +import com.sap.cloud.sdk.testutil.TestConfigurationError; + +public class TestUtility +{ + public static String readResourceFile( final Class cls, final String resourceFileName ) + { + try { + final URL resourceUrl = getResourceUrl(cls, resourceFileName); + + return Resources.toString(resourceUrl, StandardCharsets.UTF_8); + } + catch( final IOException e ) { + throw new TestConfigurationError(e); + } + } + + public static String readResourceFileCrlf( final Class cls, final String resourceFileName ) + { + return readResourceFile(cls, resourceFileName).replaceAll("(? cls, final String resourceFileName ) + { + final URL resourceUrl = cls.getClassLoader().getResource(cls.getSimpleName() + "/" + resourceFileName); + + if( resourceUrl == null ) { + throw new TestConfigurationError("Cannot find resource file with name \"" + resourceFileName + "\"."); + } + + return resourceUrl; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/BigDecimalSerialisationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/BigDecimalSerialisationTest.java new file mode 100644 index 0000000000..8782869949 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/BigDecimalSerialisationTest.java @@ -0,0 +1,65 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.Gson; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +class BigDecimalSerialisationTest +{ + private static final String TEST_DEFAULT_SERVICE_PATH = "/odata/default"; + + @NoArgsConstructor + @JsonAdapter( GsonVdmAdapterFactory.class ) + @JsonSerialize( using = JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = JacksonVdmObjectDeserializer.class ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @Data + private class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = TestEntity.class; + + @Getter + private final String defaultServicePath = TEST_DEFAULT_SERVICE_PATH; + + @ElementName( "BigDecimalField" ) + BigDecimal bigDecimalField; + + } + + @Test + void testSerialization() + throws JsonProcessingException + { + final TestEntity testEntity = new TestEntity(); + testEntity.setBigDecimalField(new BigDecimal("0.000000003")); + // GSON + final String jsonGson = new Gson().toJson(testEntity); + assertThat(jsonGson).doesNotContain("3E-9").contains("0.000000003"); + // Jackson + final String jsonJackson = new ObjectMapper().writeValueAsString(testEntity); + assertThat(jsonJackson).doesNotContain("3E-9").contains("0.000000003"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/SerializerAdapterTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/SerializerAdapterTest.java new file mode 100644 index 0000000000..8200f547b5 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/adapter/SerializerAdapterTest.java @@ -0,0 +1,79 @@ +package com.sap.cloud.sdk.datamodel.odatav4.adapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.City; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Feature; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Location; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; + +class SerializerAdapterTest +{ + private static final City CITY = + City.builder().name("Potsdam").countryRegion("Brandenburg").region("Deutschland").build(); + + private static final Trip TRIP_A = + Trip.builder().budget(100.0f).endsAt(OffsetDateTime.now()).shareId(UUID.randomUUID()).name("a").build(); + + private static final Trip TRIP_B = + Trip.builder().budget(20.0f).endsAt(OffsetDateTime.now()).shareId(UUID.randomUUID()).name("b").build(); + + private static final Person PERSON = + Person + .builder() + .lastName("Bar") + .emails(Collections.singletonList("foo@bar.com")) + .favoriteFeature(Feature.FEATURE1) + .trips(TRIP_A, TRIP_B) + .addressInfo(Collections.singletonList(new Location("Sesamstr.1", CITY))) + .build(); + + @Test + void testCompatibleNonNullGsonAndJackson() + throws JsonProcessingException + { + // GSON + final Gson gson = new Gson(); + final String jsonGson = gson.toJson(PERSON); + final Person personGson = gson.fromJson(jsonGson, Person.class); + + // Jackson + final ObjectMapper mapper = new ObjectMapper(); + mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); + final String jsonJackson = mapper.writeValueAsString(PERSON); + final Person personJackson = mapper.readValue(jsonJackson, Person.class); + + // test + assertThat(personJackson).isEqualTo(personGson); + } + + @Test + void testCompatibleNullableGsonAndJackson() + throws JsonProcessingException + { + // GSON + final Gson gson = new GsonBuilder().serializeNulls().create(); + final String jsonGson = gson.toJson(PERSON); + final Person personGson = gson.fromJson(jsonGson, Person.class); + + // Jackson + final ObjectMapper mapper = new ObjectMapper(); + final String jsonJackson = mapper.writeValueAsString(PERSON); + final Person personJackson = mapper.readValue(jsonJackson, Person.class); + + // test + assertThat(personJackson).isEqualTo(personGson); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundActionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundActionTest.java new file mode 100644 index 0000000000..8837a419e6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundActionTest.java @@ -0,0 +1,126 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Location; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class BoundActionTest +{ + private static final DefaultTrippinService service = new DefaultTrippinService().withServicePath("service-root"); + private static final Person person = Person.builder().userName("Fridolin").build(); + + @Test + void testActionOnEntityWithEtag() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.MakeHappy"; + + person.setVersionIdentifier("some-etag"); + final SingleValueActionRequestBuilder action = service.forEntity(person).applyAction(Person.makeHappy()); + + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + assertThat(action.toRequest().getActionParameters()).hasToString("{}"); + assertThat(action.toRequest().getHeaders()) + .containsEntry(HttpHeaders.IF_MATCH, Collections.singletonList("some-etag")); + } + + @Test + void testActionOnEntityNoEtag() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.MakeHappy"; + + person.setVersionIdentifier(null); + final SingleValueActionRequestBuilder action = service.forEntity(person).applyAction(Person.makeHappy()); + + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + assertThat(action.toRequest().getActionParameters()).hasToString("{}"); + assertThat(action.toRequest().getHeaders()).doesNotContainKey(HttpHeaders.IF_MATCH); + } + + @Test + void testActionWithParameters() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.MakeUnhappy"; + + final SingleValueActionRequestBuilder action = + service.forEntity(person).applyAction(Person.makeUnhappy(true)); + + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + assertThat(action.toRequest().getActionParameters()).hasToString("{\"very\":true}"); + } + + @Test + void testActionSingleToCollection() + { + final String targetUrl = "/service-root/People/Trippin.MakeAllHappy"; + + final SingleValueActionRequestBuilder action = service.applyAction(Person.makeAllHappy()); + + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testActionCollectionToCollection() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.sendMail"; + + final CollectionValueActionRequestBuilder action = + service.forEntity(person).applyAction(Person.sendMail("foo")); + + assertThat(action.toRequest().getActionParameters()).isEqualTo("{\"subject\":\"foo\"}"); + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testActionOnNestedCollection() + { + final String targetUrl = "/service-root/People('Fridolin')/Friends/Trippin.MakeAllHappy"; + + final SingleValueActionRequestBuilder action = + service.forEntity(person).navigateTo(Person.TO_FRIENDS).applyAction(Person.makeAllHappy()); + + assertThat(action.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testSuccessfulCompilation() + { + // test to ensure that all the combinations at least compile + + // 1 - 1 + final SingleValueActionRequestBuilder action1 = + service + .forEntity(person) + .applyAction( + new BoundAction.SingleToSingle<>(Person.class, Location.class, "Stuff", Collections.emptyMap())); + + // 1 - n + final CollectionValueActionRequestBuilder action2 = + service + .forEntity(person) + .applyAction( + new BoundAction.SingleToCollection<>(Person.class, String.class, "Stuff", Collections.emptyMap())); + + // n - 1 + final SingleValueActionRequestBuilder action3 = + service + .applyAction( + new BoundAction.CollectionToSingle<>(Person.class, String.class, "Stuff", Collections.emptyMap())); + + // n - n + final CollectionValueActionRequestBuilder action4 = + service + .applyAction( + new BoundAction.CollectionToCollection<>( + Person.class, + String.class, + "Stuff", + Collections.emptyMap())); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunctionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunctionTest.java new file mode 100644 index 0000000000..3d48926d42 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/BoundFunctionTest.java @@ -0,0 +1,331 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Location; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class BoundFunctionTest +{ + private static final DefaultTrippinService service = new DefaultTrippinService().withServicePath("service-root"); + private static final Person person = Person.builder().userName("Fridolin").build(); + + /* From OData V4 ABNF + + ; boundOperation segments can only be composed if the type of the previous segment + ; matches the type of the first parameter of the action or function being called. + ; Note that the rule name reflects the return type of the function. + boundOperation = "/" ( boundActionCall + / boundEntityColFunctionCall [ collectionNavigation ] + / boundEntityFunctionCall [ singleNavigation ] + / boundComplexColFunctionCall [ complexColPath ] + / boundComplexFunctionCall [ complexPath ] + / boundPrimitiveColFunctionCall [ primitiveColPath ] + / boundPrimitiveFunctionCall [ primitivePath ] + / boundFunctionCallNoParens + ) + */ + + // Problem: We don't have a good abstraction for entity sets + // Consequence: forEntity(..) doesn't work if there is more than 1 entity set for the type + // the same problem has this approach + + @Test + void testEntityToPrimitive() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.IsHappy()"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).applyFunction(Person.isHappy()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testNavigationToPrimitive() + { + final String targetUrl = "/service-root/People('Fridolin')/BestFriend/Trippin.IsHappy()"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).navigateTo(Person.TO_BEST_FRIEND).applyFunction(Person.isHappy()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testCollectionToPrimitive() + { + final String targetUrl = "/service-root/People('Fridolin')/Friends/Trippin.AreAllFriends()"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).navigateTo(Person.TO_FRIENDS).applyFunction(Person.areAllFriends()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testCollectionToCollection() + { + final String targetUrl = "/service-root/People/Trippin.MostPopularPersons()"; + + final CollectionValueFunctionRequestBuilder function = + service.applyFunction(Person.mostPopularPersons()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testCollectionOnRootEntity() + { + final String targetUrl = "/service-root/People/Trippin.AreAllFriends()"; + + final SingleValueFunctionRequestBuilder function = service.applyFunction(Person.areAllFriends()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testNavigationCollectionToPrimitive() + { + final String targetUrl = "/service-root/People('Fridolin')/Friends/Trippin.AreAllFriends()"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).navigateTo(Person.TO_FRIENDS).applyFunction(Person.areAllFriends()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testEntityToEntity() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.WorstFriend()"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).applyFunction(Person.worstFriend()); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testCompositionOnEntity() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.WorstFriend()/Friends?$top=5"; + + final GetAllRequestBuilder request = + service.forEntity(person).withFunction(Person.worstFriend()).navigateTo(Person.TO_FRIENDS).getAll().top(5); + + assertThat(request.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testCompositionOnRootEntity() + { + final String targetUrl = "/service-root/People/Trippin.MostPopularPerson()/Friends?$top=5"; + + final GetAllRequestBuilder request = + service.withFunction(Person.mostPopularPerson()).navigateTo(Person.TO_FRIENDS).getAll().top(5); + + assertThat(request.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testGetAllEntitiesAfterFunctionResult() + { + final String targetUrl = "/service-root/People/Trippin.MostPopularPersons()"; + + final GetAllRequestBuilder request = service.withFunction(Person.mostPopularPersons()).getAll(); + + assertThat(request.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testSubsequentFunctionInvocation() + { + final String targetUrl = "/service-root/People/Trippin.MostPopularPersons()/Trippin.AreAllFriends()"; + + final SingleValueFunctionRequestBuilder request = + service.withFunction(Person.mostPopularPersons()).applyFunction(Person.areAllFriends()); + + assertThat(request.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testAdditionalParameters() + { + final String targetUrl = "/service-root/People('Fridolin')/Trippin.IsHappy(really=true)"; + + final SingleValueFunctionRequestBuilder function = + service.forEntity(person).applyFunction(Person.isHappy(true)); + + assertThat(function.toRequest().getRelativeUri()).hasToString(targetUrl); + } + + @Test + void testSuccessfulCompilation() + { + // test to ensure that all the combinations at least compile + + // 1 - 1 + SingleValueFunctionRequestBuilder fun1; + fun1 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToSingleComplex<>( + Person.class, + Location.class, + "Stuff", + Collections.emptyMap())); + fun1 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToSingleEntity<>( + Person.class, + Person.class, + "Stuff", + Collections.emptyMap())); + fun1 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToSinglePrimitive<>( + Person.class, + String.class, + "Stuff", + Collections.emptyMap())); + + // 1 - n + CollectionValueFunctionRequestBuilder fun2; + fun2 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToCollectionPrimitive<>( + Person.class, + String.class, + "Stuff", + Collections.emptyMap())); + fun2 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToCollectionComplex<>( + Person.class, + Location.class, + "Stuff", + Collections.emptyMap())); + fun2 = + service + .forEntity(person) + .applyFunction( + new BoundFunction.SingleToCollectionEntity<>( + Person.class, + Person.class, + "Stuff", + Collections.emptyMap())); + + // n - 1 + SingleValueFunctionRequestBuilder fun3; + fun3 = + service + .applyFunction( + new BoundFunction.CollectionToSinglePrimitive<>( + Person.class, + String.class, + "Stuff", + Collections.emptyMap())); + fun3 = + service + .applyFunction( + new BoundFunction.CollectionToSingleComplex<>( + Person.class, + Location.class, + "Stuff", + Collections.emptyMap())); + fun3 = + service + .applyFunction( + new BoundFunction.CollectionToSingleEntity<>( + Person.class, + Person.class, + "Stuff", + Collections.emptyMap())); + + // n - n + CollectionValueFunctionRequestBuilder fun4; + fun2 = + service + .applyFunction( + new BoundFunction.CollectionToCollectionPrimitive<>( + Person.class, + String.class, + "Stuff", + Collections.emptyMap())); + fun2 = + service + .applyFunction( + new BoundFunction.CollectionToCollectionComplex<>( + Person.class, + Location.class, + "Stuff", + Collections.emptyMap())); + fun2 = + service + .applyFunction( + new BoundFunction.CollectionToCollectionEntity<>( + Person.class, + Person.class, + "Stuff", + Collections.emptyMap())); + + } + + /* + @Test + void testBoundFunctionOnPrimitiveType() + { + // Target URI: /service-root/People('Fridolin')/FirstName/Model.String.ToUpperCase() + + SingleValueFunctionRequestBuilder function = + service + .forEntity(person) + // overload this to support both primitive and complex types + .onProperty(Person.FIRST_NAME) + // Function is of type: String -> String + .applyFunction(TrippinService.PrimitiveFunctions.toUpperCase()); + }*/ + + /* + @Test + void testBoundFunctionOnComplexType() + { + // Target URI: /service-root/People('Fridolin')/AddressInfo/Model.Address.GetClosestLocation() + + SingleValueFunctionRequestBuilder function = + service + .forEntity(person) + .onProperty(Person.ADDRESS_INFO) + // Function is of type: Collection -> Location + .applyFunction(Location.getClosestLocation()); + }*/ + + /* + @Test + void testBoundFunctionOnEachEntityInCollection() + { + // Target URI: /service-root/People/$each/Trippin.isHappy() + + SingleValueFunctionRequestBuilder function = + service + // overload this to support both functions on single entities and collections + // automatically apply $each if the function is of type: EntityT -> Any + .applyFunction(Person.isHappy()); + + }*/ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilderTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilderTest.java new file mode 100644 index 0000000000..c00f9fb29b --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueActionRequestBuilderTest.java @@ -0,0 +1,236 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.TestUtility; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@WireMockTest +class CollectionValueActionRequestBuilderTest +{ + private static final String DEFAULT_SERVICE_PATH = "/odata/default"; + private static final String ODATA_ACTION = "TestAction"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + stubFor(head(anyUrl()).willReturn(ok())); + } + + private static String readResourceFile( final String resourceFileName ) + { + return TestUtility.readResourceFile(CollectionValueActionRequestBuilderTest.class, resourceFileName); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "com.sap.cloud.sdk.TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = + SingleValueFunctionRequestBuilderTest.TestEntity.class; + + @Override + protected String getDefaultServicePath() + { + return DEFAULT_SERVICE_PATH; + } + + @ElementName( "Name" ) + @SerializedName( "Name" ) + @JsonProperty( "Name" ) + private String name; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class ComplexType extends VdmComplex + { + + @Getter + private final String odataType = "com.sap.cloud.sdk.ComplexType"; + + @Getter + private final Class type = + SingleValueActionRequestBuilderTest.ComplexType.class; + + @ElementName( "City" ) + private String city; + + @ElementName( "Country" ) + private String country; + } + + @Test + void testActionWithPrimitiveResponse() + { + stubFor( + post(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .willReturn(okJson("{\"value\" : [ 3.14, 9.81 ]}"))); + + final CollectionValueActionRequestBuilder sut = + new CollectionValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, Float.class); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + + final ActionResponseCollection actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).hasSize(2); + assertThat(actualResponse.getResponseResult().get()).contains(3.14f, 9.81f); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithStringResponse() + { + + stubFor( + post(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .willReturn(okJson("{" + "\"value\" : [ \"It\", \"works\",\"as\",\"expected\" ]" + "}"))); + + final CollectionValueActionRequestBuilder sut = + new CollectionValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, String.class); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + + final ActionResponseCollection actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).hasSize(4); + assertThat(actualResponse.getResponseResult().get()).contains("It", "works", "as", "expected"); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithComplexTypeResponse() + { + + stubFor(post(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)).willReturn(okJson(""" + { "value" : [\ + { "City" : "Stockholm" ,"Country" : "Sweden"},\ + { "City" : "Dubrovnik","Country" : "Croatia" }\ + ]}\ + """))); + + final CollectionValueActionRequestBuilder sut = + new CollectionValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, ComplexType.class); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + final ActionResponseCollection actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).hasSize(2); + assertThat(actualResponse.getResponseResult().get()) + .contains(new ComplexType("Stockholm", "Sweden"), new ComplexType("Dubrovnik", "Croatia")); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithEntityResponse() + { + stubFor( + post(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .willReturn( + okJson("{ \"value\" : [" + "{ \"Name\" : \"Tester1\" }," + "{ \"Name\" : \"Tester2\" }" + "]}"))); + + final CollectionValueActionRequestBuilder sut = + new CollectionValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, TestEntity.class); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + final ActionResponseCollection actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).hasSize(2); + assertThat(actualResponse.getResponseResult().get()) + .contains(new TestEntity("Tester1"), new TestEntity("Tester2")); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilderTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilderTest.java new file mode 100644 index 0000000000..320c05d77a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CollectionValueFunctionRequestBuilderTest.java @@ -0,0 +1,182 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@WireMockTest +class CollectionValueFunctionRequestBuilderTest +{ + private static final String SERVICE_PATH = "/odata/default"; + private static final String FUNCTION_NAME = "TestFunction"; + private static final ODataFunctionParameters FUNCTION_PARAMETERS; + private static final Map FUNCTION_PARAMETER_MAP = new HashMap<>(); + + static { + FUNCTION_PARAMETER_MAP.put("stringParameter", "test"); + FUNCTION_PARAMETER_MAP.put("booleanParameter", true); + FUNCTION_PARAMETER_MAP.put("integerParameter", 9000); + FUNCTION_PARAMETER_MAP.put("decimalParameter", 3.14); + FUNCTION_PARAMETER_MAP.put("durationParameter", Duration.ofHours(8)); + FUNCTION_PARAMETER_MAP.put("dateTimeParameter", LocalDateTime.of(2019, 12, 25, 8, 0, 0)); + + FUNCTION_PARAMETERS = ODataFunctionParameters.of(FUNCTION_PARAMETER_MAP, ODataProtocol.V4); + } + + private static final String ODATA_FUNCTION_PARAMETER_WITH_SPECIAL_CHARACTER = "(stringParameter='t''est')"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "com.sap.cloud.sdk.TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = + SingleValueFunctionRequestBuilderTest.TestEntity.class; + + @Override + protected String getDefaultServicePath() + { + return SERVICE_PATH; + } + + @ElementName( "Name" ) + @SerializedName( "Name" ) + @JsonProperty( "Name" ) + private String name; + } + + @Test + void testFunctionQueryWithoutParameters() + { + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()).hasToString(SERVICE_PATH + '/' + FUNCTION_NAME + "()"); + } + + @Test + void testFunctionQueryWithMethodParameters() + { + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder( + SERVICE_PATH, + FUNCTION_NAME, + FUNCTION_PARAMETER_MAP, + Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(SERVICE_PATH + '/' + FUNCTION_NAME + FUNCTION_PARAMETERS.toEncodedString()); + } + + @Test + void testFunctionQueryWithSpecialCharactersInMethodParameters() + { + final ODataFunctionParameters parameters = + new ODataFunctionParameters(ODataProtocol.V4).addParameter("stringParameter", "t'est"); + final ODataResourcePath functionPath = ODataResourcePath.of(FUNCTION_NAME, parameters); + + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder<>(SERVICE_PATH, functionPath, Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(SERVICE_PATH + '/' + FUNCTION_NAME + ODATA_FUNCTION_PARAMETER_WITH_SPECIAL_CHARACTER); + } + + @Test + void testFunctionQueryWithMapParameters() + { + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder<>( + SERVICE_PATH, + FUNCTION_NAME, + FUNCTION_PARAMETER_MAP, + Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(SERVICE_PATH + '/' + FUNCTION_NAME + FUNCTION_PARAMETERS.toEncodedString()); + } + + @Test + void testFunctionWithPrimitiveResponse() + { + stubFor( + get(urlPathEqualTo(SERVICE_PATH + '/' + FUNCTION_NAME + "()")) + .willReturn(okJson("{" + "\"value\" : [ 3.14f, 9.81f ]" + "}"))); + + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, Float.class); + + final List actualResponse = requestBuilder.execute(destination); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse).hasSize(2); + assertThat(actualResponse).contains(3.14f, 9.81f); + } + + @Test + void testFunctionWithEntityResponse() + { + stubFor( + get(urlPathEqualTo(SERVICE_PATH + '/' + FUNCTION_NAME + "()")) + .willReturn( + okJson("{ \"value\" : [" + "{ \"Name\" : \"Tester1\" }," + "{ \"Name\" : \"Tester2\" }" + "]}"))); + + final CollectionValueFunctionRequestBuilder requestBuilder = + new CollectionValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class); + + final List actualResponse = requestBuilder.execute(destination); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse).hasSize(2); + assertThat(actualResponse).contains(new TestEntity("Tester1"), new TestEntity("Tester2")); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CustomFilterExpressionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CustomFilterExpressionTest.java new file mode 100644 index 0000000000..8d3fa9181a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CustomFilterExpressionTest.java @@ -0,0 +1,67 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ValueBoolean; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class CustomFilterExpressionTest +{ + @Test + void requestBuilderHasExpectedParameters() + { + final ValueBoolean untypedExpression = FieldReference.of("MiddleName").equalTo("SomeName"); + final FilterableBoolean customFilterExpression = + FilterableBoolean.fromCustomFilter(untypedExpression, Person.class); + + GetAllRequestBuilder requestBuilder = + new DefaultTrippinService().getAllPeople().filter(customFilterExpression); + assertThat(requestBuilder.toRequest().getRelativeUri()).hasParameter("$filter", "(MiddleName eq 'SomeName')"); + } + + @Test + void requestBuilderHasExpectedComplexParameters() + { + final ValueBoolean untypedMultiExpression = + FieldReference + .of("MiddleName") + .equalTo("SomeName") + .and(FieldReference.of("YearsOfExperience").greaterThan(5)); + final FilterableBoolean customFilterExpression = + FilterableBoolean.fromCustomFilter(untypedMultiExpression, Person.class); + + GetAllRequestBuilder requestBuilder = + new DefaultTrippinService().getAllPeople().filter(customFilterExpression); + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasParameter("$filter", "((MiddleName eq 'SomeName') and (YearsOfExperience gt 5))"); + + } + + @Test + void entityTypeIsCorrect() + { + final ValueBoolean untypedExpression = FieldReference.of("MiddleName").equalTo("SomeName"); + final FilterableBoolean customFilterExpression = + FilterableBoolean.fromCustomFilter(untypedExpression, Person.class); + + assertThat(customFilterExpression.getEntityType()).isEqualTo(Person.class); + } + + @Test + void expressionDelegatedSuccessfully() + { + final ValueBoolean untypedExpression = FieldReference.of("MiddleName").equalTo("SomeName"); + final FilterableBoolean customFilterExpression = + FilterableBoolean.fromCustomFilter(untypedExpression, Person.class); + + assertThat(customFilterExpression.getExpression(ODataProtocol.V4)) + .contains(untypedExpression.getExpression(ODataProtocol.V4)); + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java new file mode 100644 index 0000000000..ca571e03ef --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/FieldSerializationTest.java @@ -0,0 +1,239 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +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; + +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; + +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 + { + @Getter + private final String odataType = "TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = ReferenceObject.class; + + @ElementName( "ByteValue" ) + @SerializedName( "ByteValue" ) + @JsonProperty( "ByteValue" ) + short byteValue; + + @ElementName( "SByteValue" ) + @SerializedName( "SByteValue" ) + @JsonProperty( "SByteValue" ) + Byte sByteValue; + + @ElementName( "Int16Value" ) + @SerializedName( "Int16Value" ) + @JsonProperty( "Int16Value" ) + short int16Value; + + @ElementName( "Int32Value" ) + @SerializedName( "Int32Value" ) + @JsonProperty( "Int32Value" ) + int int32Value; + + @ElementName( "Int64Value" ) + @SerializedName( "Int64Value" ) + @JsonProperty( "Int64Value" ) + long int64Value; + + @ElementName( "SingleValue" ) + @SerializedName( "SingleValue" ) + @JsonProperty( "SingleValue" ) + float singleValue; + + @ElementName( "DoubleValue" ) + @SerializedName( "DoubleValue" ) + @JsonProperty( "DoubleValue" ) + double doubleValue; + + @ElementName( "DecimalValue" ) + @SerializedName( "DecimalValue" ) + @JsonProperty( "DecimalValue" ) + BigDecimal decimalValue; + + @ElementName( "BooleanValue" ) + @SerializedName( "BooleanValue" ) + @JsonProperty( "BooleanValue" ) + boolean booleanValue; + + @ElementName( "StringValue" ) + @SerializedName( "StringValue" ) + @JsonProperty( "StringValue" ) + String stringValue; + + @ElementName( "BinaryValue" ) + @SerializedName( "BinaryValue" ) + @JsonProperty( "BinaryValue" ) + byte[] binaryValue; + + @ElementName( "GuidValue" ) + @SerializedName( "GuidValue" ) + @JsonProperty( "GuidValue" ) + UUID guidValue; + + @ElementName( "TimeOfDayValue" ) + @SerializedName( "TimeOfDayValue" ) + @JsonProperty( "TimeOfDayValue" ) + LocalTime timeOfDayValue; + + @ElementName( "DateValue" ) + @SerializedName( "DateValue" ) + @JsonProperty( "DateValue" ) + LocalDate dateValue; + + @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]}\ + }\ + """; + + 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\""); + + static final String Base_64 = "+//v"; + static final String Base_64_Url = "-__v"; + } + + @Test + 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 }); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).isEqualTo(ReferenceObject.PAYLOAD_ODATA_REFERENCE); + } + + @Test + 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(Base64.getUrlDecoder().decode(ReferenceObject.Base_64_Url)); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); + } + + @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); + } + + @Test + 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()).isEqualTo(Base64.getDecoder().decode(ReferenceObject.Base_64)); + + final String ser = + new CreateRequestBuilder<>("/", referenceResult, "EntityCollection").toRequest().getSerializedEntity(); + assertThat(ser).contains("\"BinaryValue\":\"" + ReferenceObject.Base_64 + "\""); + } + + @SneakyThrows + private static ODataRequestResultGeneric mockRequestResult( final String payload ) + { + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + when(request.getProtocol()).thenReturn(ODataProtocol.V4); + + final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); + response.setEntity(new StringEntity(payload)); + response.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + + return new ODataRequestResultGeneric(request, response); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HeadersHandlingTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HeadersHandlingTest.java new file mode 100644 index 0000000000..33f57a676e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HeadersHandlingTest.java @@ -0,0 +1,135 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +@WireMockTest +class HeadersHandlingTest +{ + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + private static final String SERVICE_PATH = "/remoteService"; + private static final String ENTITY_URL = SERVICE_PATH + "/People('usr')"; + + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + private static final String SET_COOKIE_HEADER_KEY = "Set-Cookie"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Test + void testUpdateRequestContainsHeadersWithNonUniqueKeys() + { + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + + stubFor( + patch(urlEqualTo(ENTITY_URL)) + .willReturn( + ok() + .withHeader(SET_COOKIE_HEADER_KEY, "SimpleKey=SimpleValue") + .withHeader(SET_COOKIE_HEADER_KEY, "KeyWithoutAValue") + .withHeader(SET_COOKIE_HEADER_KEY, "KeyWithCommaValue=Value, which contains a comma") + .withHeader( + SET_COOKIE_HEADER_KEY, + "MultiValueKey1=Value 1, with comma; MultiValueKey2=Value 2 ; MultiValueKey3;"))); + + final Person person = new Person(); + person.setUserName("usr"); + + final UpdateRequestBuilder builder = new UpdateRequestBuilder<>(SERVICE_PATH, person, "People"); + + final ModificationResponse response = builder.execute(destination); + + final List responseHeaders = + ImmutableList.copyOf(response.getResponseHeaders().get(SET_COOKIE_HEADER_KEY)); + + assertThat(responseHeaders) + .containsExactly( + "SimpleKey=SimpleValue", + "KeyWithoutAValue", + "KeyWithCommaValue=Value, which contains a comma", + "MultiValueKey1=Value 1, with comma; MultiValueKey2=Value 2 ; MultiValueKey3;"); + } + + @Test + void testMultipleHeadersWithSameKey() + { + final Person person = new Person(); + person.setUserName("usr"); + + // patch request with 2 cookie headers with the same key + final UpdateRequestBuilder builder = + new UpdateRequestBuilder<>(SERVICE_PATH, person, "People") + .withHeader(SET_COOKIE_HEADER_KEY, "foo") + .withHeader(SET_COOKIE_HEADER_KEY, "bar"); + + executeMultipleHeadersWithSameKey(builder); + } + + @Test + void testMultipleHeadersWithSameKeyAtOnce() + { + final Person person = new Person(); + person.setUserName("usr"); + + // patch request with 2 cookie headers with the same key + final UpdateRequestBuilder builder = + new UpdateRequestBuilder<>(SERVICE_PATH, person, "People") + .withHeaders(ImmutableMap.of(SET_COOKIE_HEADER_KEY, "foo")) + .withHeaders(ImmutableMap.of(SET_COOKIE_HEADER_KEY, "bar")); + + executeMultipleHeadersWithSameKey(builder); + } + + public void executeMultipleHeadersWithSameKey( UpdateRequestBuilder builder ) + { + // With Apache HttpClient 5 the CSRF token HEAD request is issued by the client-level + // CsrfTokenInterceptor, which does not propagate the request's custom headers. The custom + // headers are only sent on the actual (PATCH) request, so that is where we assert them. + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + + // the server will check for the cookie headers when receiving the patch request + stubFor( + patch(urlEqualTo(ENTITY_URL)) + .withHeader(SET_COOKIE_HEADER_KEY, equalTo("foo")) + .withHeader(SET_COOKIE_HEADER_KEY, equalTo("bar")) + .willReturn(ok())); + + builder.execute(destination); + + // check that both header values with the same key are sent on the request + verify( + patchRequestedFor(urlEqualTo(ENTITY_URL)) + .withHeader(SET_COOKIE_HEADER_KEY, equalTo("foo")) + .withHeader(SET_COOKIE_HEADER_KEY, equalTo("bar"))); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HttpResponseEvaluationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HttpResponseEvaluationTest.java new file mode 100644 index 0000000000..9bedcf186a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/HttpResponseEvaluationTest.java @@ -0,0 +1,204 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON; +import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; + +import lombok.SneakyThrows; + +public class HttpResponseEvaluationTest +{ + private static final Destination DESTINATION = DefaultHttpDestination.builder("foo").build(); + + private HttpClient httpClient; + private BasicClassicHttpResponse httpResponse; + private InputStreamEntity httpEntity; + private InputStream inputStream; + + @SneakyThrows + void mockHttpResponse( final ContentType contentType, final String payload ) + { + httpClient = mock(HttpClient.class); + inputStream = spy(new ByteArrayInputStream(payload.getBytes(UTF_8))); + httpEntity = spy(new InputStreamEntity(inputStream, contentType)); + httpResponse = spy(new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK")); + httpResponse.setEntity(httpEntity); + ApacheHttpClient5Accessor.setHttpClientFactory(destination -> httpClient); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(httpResponse); + } + + @AfterEach + void teardown() + { + ApacheHttpClient5Accessor.setHttpClientFactory(null); + ApacheHttpClient5Accessor.setHttpClientCache(null); + } + + @SneakyThrows + @Test + void testCreate() + { + mockHttpResponse(APPLICATION_JSON, "{}"); + + final ModificationResponse result = + new CreateRequestBuilder<>("/path", new TestEntity(), "TestEntitySet").execute(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testUpdate() + { + mockHttpResponse(APPLICATION_JSON, "{}"); + + final TestEntity testEntity = new TestEntity(); + testEntity.setId("id"); + + final ModificationResponse result = + new UpdateRequestBuilder<>("/path", testEntity, "TestEntitySet").execute(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testDelete() + { + mockHttpResponse(APPLICATION_JSON, "{}"); + + final ModificationResponse result = + new DeleteRequestBuilder<>("/path", new TestEntity(), "TestEntitySet").execute(DESTINATION); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + + assertThat(result.getResponseStatusCode()).isEqualTo(200); + } + + @SneakyThrows + @Test + void testReadAll() + { + mockHttpResponse(APPLICATION_JSON, "{\"value\": []}"); + + final List result = + new GetAllRequestBuilder<>("/path", TestEntity.class, "TestEntitySet").execute(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testReadByKey() + { + mockHttpResponse(APPLICATION_JSON, "{}"); + + final TestEntity result = + new GetByKeyRequestBuilder<>("/path", TestEntity.class, Map.of("id", "foo"), "TestEntitySet") + .execute(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testReadCount() + { + mockHttpResponse(TEXT_PLAIN, "42"); + + final Long result = new CountRequestBuilder<>("/path", TestEntity.class, "TestEntitySet").execute(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testAction() + { + mockHttpResponse(APPLICATION_JSON, "{}"); + + final ActionResponseSingle result = + new SingleValueActionRequestBuilder<>("/path", "ActionName", TestEntity.class).execute(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + @SneakyThrows + @Test + void testFunction() + { + mockHttpResponse(APPLICATION_JSON, "{\"value\":[]}"); + + final List result = + new CollectionValueFunctionRequestBuilder<>("/path", "FunctionName", TestEntity.class).execute(DESTINATION); + + assertThat(result).isNotNull(); + + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + verify(httpResponse, times(1)).getEntity(); + verify(httpEntity, times(1)).writeTo(any(OutputStream.class)); + verify(inputStream, times(2)).close(); + } + + // count function action +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponseTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponseTest.java new file mode 100644 index 0000000000..d6d26218c7 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationResponseTest.java @@ -0,0 +1,164 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V4; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.apache.hc.core5.http.message.BasicHeader; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odata.client.request.UpdateStrategy; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.ToString; + +class ModificationResponseTest +{ + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestObject extends VdmEntity + { + @Getter + private final String odataType = "TestObject"; + + @Getter + private final Class type = TestObject.class; + + @ElementName( "foo" ) + private String name; + + @Nonnull + @Override + protected String getEntityCollection() + { + return odataType; + } + } + + @Test + void testEntityResponse() + { + final TestObject inputObject = new TestObject(); + + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + when(request.getProtocol()).thenReturn(V4); + + final Header[] responseHeaders = { new BasicHeader("fizz", "buzz"), new BasicHeader("fizz", "fuzz, bizz=1") }; + + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(responseHeaders).when(response).getHeaders(); + doReturn(responseHeaders).when(response).getHeaders("ETag"); + doReturn(new StringEntity("{\"foo\":\"bar\"}", UTF_8)).when(response).getEntity(); + doReturn(HttpStatus.SC_OK).when(response).getCode(); + + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final ModificationResponse modification = ModificationResponse.of(result, inputObject); + + assertThat(modification).isNotNull(); + assertThat(modification.getResponseStatusCode()).isEqualTo(HttpStatus.SC_OK); + assertThat(modification.getRequestEntity()).isSameAs(inputObject); + + assertThat(modification.getResponseEntity().get()).isNotSameAs(inputObject); + assertThat(modification.getResponseEntity().get()).isEqualTo(new TestObject("bar")); + assertThat(modification.getModifiedEntity()).isEqualTo(new TestObject("bar")); + + assertThat(modification.getResponseHeaders()).containsOnlyKeys("fizz"); + assertThat(modification.getResponseHeaders().get("fizz")).containsExactly("buzz", "fuzz, bizz=1"); + } + + @Test + void testEmptyResponse() + { + final TestObject inputObject = new TestObject(); + + final ODataRequestGeneric request = mock(ODataRequestGeneric.class); + when(request.getProtocol()).thenReturn(V4); + + final ClassicHttpResponse response = mock(ClassicHttpResponse.class); + doReturn(new Header[0]).when(response).getHeaders(); + doReturn(new Header[0]).when(response).getHeaders("ETag"); + doReturn(new StringEntity("", UTF_8)).when(response).getEntity(); + doReturn(HttpStatus.SC_NO_CONTENT).when(response).getCode(); + + final ODataRequestResultGeneric result = new ODataRequestResultGeneric(request, response); + final ModificationResponse modification = ModificationResponse.of(result, inputObject); + + assertThat(modification).isNotNull(); + assertThat(modification.getResponseStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT); + assertThat(modification.getRequestEntity()).isSameAs(inputObject); + assertThat(modification.getModifiedEntity()).isNotSameAs(inputObject); + assertThat(modification.getModifiedEntity()).isEqualTo(inputObject); + assertThat(modification.getResponseHeaders()).isEmpty(); + } + + @SneakyThrows + @Test + void testResponseIsOnlyEvaluatedOnce() + { + final TestObject inputObject = new TestObject(); + + final ClassicHttpResponse response = spy(new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK")); + response.setHeaders(new Header[0]); + response.setEntity(new StringEntity("{\"foo\":\"bar\"}", UTF_8)); + + final ODataEntityKey key = ODataEntityKey.of(Map.of("id", 42), V4); + final ODataRequestUpdate request = + new ODataRequestUpdate("service/path", "EntitySet", key, "{}", UpdateStrategy.REPLACE_WITH_PUT, null, V4); + + final HttpClient httpClient = mock(HttpClient.class); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response); + + final ODataRequestResultGeneric result = request.execute(httpClient); + final ModificationResponse modification = ModificationResponse.of(result, inputObject); + + modification.getResponseEntity(); + final Option responseEntity = modification.getResponseEntity(); + assertThat(responseEntity).isNotNull(); + + modification.getModifiedEntity(); + final TestObject modifiedEntity = modification.getModifiedEntity(); + assertThat(modifiedEntity).isNotNull(); + + verify(response, times(1)).getEntity(); + verify(httpClient, times(1)).executeOpen(isNull(), any(), isNull()); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/NestedEntityTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/NestedEntityTest.java new file mode 100644 index 0000000000..8edbdbb9a3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/NestedEntityTest.java @@ -0,0 +1,413 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCount; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestDelete; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestUpdate; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.PlanItem; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +@WireMockTest +class NestedEntityTest +{ + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + + private DefaultHttpDestination destination; + private DefaultTrippinService service; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + service = new DefaultTrippinService().withServicePath("/TripPinServiceRW"); + } + + @Test + void testGetSingleNestedFriend() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestReadByKey getAll = + service.forEntity(personByKey).navigateTo(Person.TO_BEST_FRIEND).get().toRequest(); + + assertThat(getAll.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/BestFriend"); + } + + @Test + void testGetSingleNestedNestedNestedFriend() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestReadByKey getAll = + service + .forEntity(personByKey) + .navigateTo(Person.TO_BEST_FRIEND) + .navigateTo(Person.TO_BEST_FRIEND) + .navigateTo(Person.TO_BEST_FRIEND) + .get() + .toRequest(); + + assertThat(getAll.getRelativeUri()) + .hasToString("/TripPinServiceRW/People('russellwhyte')/BestFriend/BestFriend/BestFriend"); + } + + @Test + void testDeleteSingleNestedFriend() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestDelete delete = + service.forEntity(personByKey).navigateTo(Person.TO_BEST_FRIEND).delete().toRequest(); + + assertThat(delete.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/BestFriend"); + } + + @Test + void testUpdateSingleNestedFriend() + { + final Person friend = new Person(); + friend.setLastName("Jobs"); + + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestUpdate update = + service.forEntity(personByKey).navigateTo(Person.TO_BEST_FRIEND).update(friend).toRequest(); + + assertThat(update.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/BestFriend"); + } + + @Test + void testCreateNestedTrip() + { + final Trip customTrip = new Trip(); + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestCreate create = + service.forEntity(personByKey).navigateTo(Person.TO_TRIPS).create(customTrip).toRequest(); + + assertThat(create.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/Trips"); + } + + @Test + void testGetAllNestedTrips() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestRead getAll = service.forEntity(personByKey).navigateTo(Person.TO_TRIPS).getAll().toRequest(); + + assertThat(getAll.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/Trips"); + } + + @Test + void testDeleteBestFriend() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestDelete delete = + service + .forEntity(personByKey) + .navigateTo(Person.TO_BEST_FRIEND) + .delete() + .matchAnyVersionIdentifier() + .toRequest(); + + assertThat(delete.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/BestFriend"); + } + + @Test + void testUpdateBestFriend() + { + final Person oldPerson = Person.builder().userName("oldMan").build(); + final Person newPerson = Person.builder().userName("youngMan").build(); + + final ODataRequestUpdate update = + service + .forEntity(oldPerson) + .navigateTo(Person.TO_BEST_FRIEND) + .update(newPerson) + .matchAnyVersionIdentifier() + .toRequest(); + + assertThat(update.getRelativeUri()).hasToString("/TripPinServiceRW/People('oldMan')/BestFriend"); + } + + @Test + void testCountNestedTrip() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + final ODataRequestCount count = service.forEntity(personByKey).navigateTo(Person.TO_TRIPS).count().toRequest(); + + assertThat(count.getRelativeUri()).hasToString("/TripPinServiceRW/People('russellwhyte')/Trips/$count"); + } + + @Test + void testCountNestedTripToRequestIsStable() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + final CountRequestBuilder countBuilder = + service.forEntity(personByKey).navigateTo(Person.TO_TRIPS).count(); + + assertThat(countBuilder.toRequest().getRelativeUri()) + .hasToString("/TripPinServiceRW/People('russellwhyte')/Trips/$count"); + assertThat(countBuilder.toRequest().getRelativeUri()) + .hasToString("/TripPinServiceRW/People('russellwhyte')/Trips/$count"); + } + + @Test + void testGetByKeyNestedPlanItem() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + final Trip tripByKey = Trip.builder().tripId(1003).build(); + + final GetAllRequestBuilder builder = + service + .forEntity(personByKey) + .navigateTo(Person.TO_TRIPS) + .forEntity(tripByKey) + .navigateTo(Trip.TO_PLAN_ITEMS) + .getAll(); + + final ODataRequestRead getAll = builder.toRequest(); + + assertThat(getAll.getRelativeUri()) + .hasToString("/TripPinServiceRW/People('russellwhyte')/Trips(1003)/PlanItems"); + } + + @Test + void testCreateNestedPlanItem() + { + final PlanItem customPlanItem = new PlanItem(); + final Person personByKey = Person.builder().userName("russellwhyte").build(); + final Trip tripByKey = Trip.builder().tripId(1003).build(); + + final CreateRequestBuilder createdPlanItem = + service + .forEntity(personByKey) + .navigateTo(Person.TO_TRIPS) + .forEntity(tripByKey) + .navigateTo(Trip.TO_PLAN_ITEMS) + .create(customPlanItem) + .withHeader("header", "value") + .withQueryParameter("query", "parameter"); + + // check client + final ODataRequestCreate requestCreate = createdPlanItem.toRequest(); + assertThat(requestCreate.getRelativeUri()).hasQuery("query=parameter"); + assertThat(requestCreate.getRelativeUri()) + .hasPath("/TripPinServiceRW/People('russellwhyte')/Trips(1003)/PlanItems"); + + // check VDM execution + final String headPath = "/TripPinServiceRW"; + final String postPath = "/TripPinServiceRW/People('russellwhyte')/Trips(1003)/PlanItems"; + + stubFor(head(urlPathEqualTo(headPath)).willReturn(ok().withHeader("x-csrf-token", "foo"))); + stubFor( + post(urlPathEqualTo(postPath)) + .withQueryParam("query", equalTo("parameter")) + .withHeader("header", equalTo("value")) + .willReturn(okJson("{}"))); + + final ModificationResponse createResponse = createdPlanItem.execute(destination); + assertThat(createResponse).isNotNull(); + assertThat(createResponse.getResponseStatusCode()).isEqualTo(200); + verify(1, postRequestedFor(urlPathEqualTo(postPath))); + } + + @Test + void testDeleteEntityWithEtag() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + personByKey.setVersionIdentifier("foobar"); + + final String headPath = service.getServicePath(); + final String deletePath = headPath + "/People('russellwhyte')"; + + stubFor(head(urlPathEqualTo(headPath)).willReturn(ok().withHeader("x-csrf-token", "foo"))); + stubFor(delete(urlPathEqualTo(deletePath)).willReturn(okJson("{}"))); + + final DeleteRequestBuilder delete = service.forEntity(personByKey).delete(); + + assertThat(delete.toRequest().getRelativeUri()).hasToString(deletePath); + + delete.execute(destination); + + verify(deleteRequestedFor(urlEqualTo(deletePath)).withHeader(HttpHeaders.IF_MATCH, equalTo("foobar"))); + } + + @Test + void testUpdateEntityWithEtag() + { + final Person personByKey = Person.builder().userName("russellwhyte").build(); + + personByKey.setVersionIdentifier("foobar"); + + final String headPath = service.getServicePath(); + final String updatePath = headPath + "/People('russellwhyte')"; + + stubFor(head(urlPathEqualTo(headPath)).willReturn(ok().withHeader("x-csrf-token", "foo"))); + stubFor(patch(urlPathEqualTo(updatePath)).willReturn(okJson("{}"))); + + final UpdateRequestBuilder update = service.forEntity(personByKey).update(personByKey); + + assertThat(update.toRequest().getRelativeUri()).hasToString(updatePath); + + update.execute(destination); + + verify(patchRequestedFor(urlEqualTo(updatePath)).withHeader(HttpHeaders.IF_MATCH, equalTo("foobar"))); + } + + @Test + void testTypeRestrictionForVdmEntitySet() + { + final EntityWithoutEntitySet entityWithoutEntitySet = new EntityWithoutEntitySet(); + final EntityWithEntitySet entityWithEntitySet = new EntityWithEntitySet(); + + assertThatIllegalStateException().isThrownBy(() -> service.forEntity(entityWithoutEntitySet)); + assertThatNoException().isThrownBy(() -> service.forEntity(entityWithEntitySet)); + + assertThatNoException().isThrownBy(() -> { + service + .forEntity(entityWithEntitySet) + .navigateTo(EntityWithEntitySet.NAVIGATION_PROPERTY) + .forEntity(entityWithoutEntitySet); + }); + } + + private static class EntityWithoutEntitySet extends VdmEntity + { + @Nonnull + @Override + protected String getEntityCollection() + { + return "outside-of-entity-set"; + } + + @Nonnull + @Override + public String getOdataType() + { + return getEntityCollection(); + } + + @Nonnull + @Override + public Class getType() + { + return EntityWithoutEntitySet.class; + } + } + + private static class EntityWithEntitySet extends VdmEntity implements VdmEntitySet + { + public static final NavigationProperty.Collection NAVIGATION_PROPERTY = + new NavigationProperty.Collection<>(EntityWithEntitySet.class, "Navigations", EntityWithoutEntitySet.class); + + @Nonnull + @Override + protected String getEntityCollection() + { + return "outside-of-entity-set"; + } + + @Nonnull + @Override + public String getOdataType() + { + return getEntityCollection(); + } + + @Nonnull + @Override + public Class getType() + { + return EntityWithEntitySet.class; + } + } + + /* Commented out since otherwise this leads to a traceability mapping failure + @Disabled( "Use this to run and check against the reference OData service." ) + @Test + void integrationTestCreateNestedEntity() + throws IOException + { + final HttpDestination destination = TripPinUtility.getDestinationRW(); + + final String RANDOM_CONFIRMATION_CODE = "" + new Random().nextLong(); + + final PlanItem somePlanItem = + PlanItem + .builder() + .confirmationCode(RANDOM_CONFIRMATION_CODE) + .duration(Duration.ofHours(3)) + .endsAt(LocalDate.of(2014, Month.JUNE, 1).atStartOfDay().atOffset(ZoneOffset.UTC)) + .startsAt(LocalDate.of(2014, Month.MAY, 1).atStartOfDay().atOffset(ZoneOffset.UTC)) + .build(); + + final Person personByKey = Person.builder().userName("russellwhyte").build(); + final Trip tripByKey = Trip.builder().tripId(1003L).build(); + + // Create new item + final ModificationResponse createResult = + service + .forEntity(personByKey) + .navigateTo(Person.TO_TRIPS) + .forEntity(tripByKey) + .navigateTo(Trip.PLAN_ITEMS) + .create(somePlanItem) + .execute(destination); + + // Assert on positive feedback + assertThat(createResult).isNotNull(); + + // Query newly created item + final Person personFound = + service + .getPersonsByKey("russellwhyte") + .select( + Person.TO_TRIPS.filter(Trip.TRIP_ID.equalTo(1003L)).select( + Trip.PLAN_ITEMS.select(PlanItem.CONFIRMATION_CODE))) + .execute(destination); + + // Find item in response + assertThat(personFound).isNotNull(); + assertThat(personFound.getTrips()).isNotEmpty().anySatisfy( + trip -> assertThat(trip.getPlanItems()) + .anySatisfy(item -> assertThat(item.getConfirmationCode()).isEqualTo(RANDOM_CONFIRMATION_CODE))); + } + */ +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataRequestImplTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataRequestImplTest.java new file mode 100644 index 0000000000..ae3b3b20e8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataRequestImplTest.java @@ -0,0 +1,54 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; + +class ODataRequestImplTest +{ + @Test + void testRequestWithoutNestedRequests() + { + final NavigationPropertyCollectionQuery request = + NavigationPropertyCollectionQuery.ofSubQuery("ASDF"); + + request + .select(Person.FIRST_NAME, Person.LAST_NAME) + .filter(Person.FIRST_NAME.contains("foo")) + .top(5) + .skip(3) + .orderBy(Person.USER_NAME.desc()); + + final String expected = + "$select=FirstName,LastName;$filter=contains(FirstName,'foo');$top=5;$skip=3;$orderby=UserName%20desc"; + final String unencodedExpected = + "$select=FirstName,LastName;$filter=contains(FirstName,'foo');$top=5;$skip=3;$orderby=UserName desc"; + + assertThat(request.getEncodedQueryString()).isEqualTo(expected); + assertThat(request.getQueryString()).isEqualTo(unencodedExpected); + } + + @Test + void testRequestWithNestedRequests() + { + final NavigationPropertyCollectionQuery request = + NavigationPropertyCollectionQuery.ofSubQuery("ASDF"); + + request + .select(Person.FIRST_NAME, Person.LAST_NAME) + .select(Person.TO_BEST_FRIEND.select(Person.TO_TRIPS.select(Trip.DESCRIPTION).top(10))) + .filter(Person.FIRST_NAME.contains("foo")); + + final String expected = + "$select=FirstName,LastName;$expand=BestFriend($expand=Trips($select=Description;$top=10));$filter=contains(FirstName,'foo')"; + final String unencodedExpected = + "$select=FirstName,LastName;$expand=BestFriend($expand=Trips($select=Description;$top=10));$filter=contains(FirstName,'foo')"; + + assertThat(request.getEncodedQueryString()).isEqualTo(expected); + assertThat(request.getQueryString()).isEqualTo(unencodedExpected); + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataV4BatchRequestUnitTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataV4BatchRequestUnitTest.java new file mode 100644 index 0000000000..229d4f2d88 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODataV4BatchRequestUnitTest.java @@ -0,0 +1,300 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.sap.cloud.sdk.datamodel.odatav4.TestUtility.readResourceFileCrlf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; + +import lombok.SneakyThrows; + +@WireMockTest +class ODataV4BatchRequestUnitTest +{ + private static final TestEntityService SERVICE = new TestEntityService() + { + }; + + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_FETCH_VALUE = "fetch"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + private static final String REQUEST_URL_BATCH = "/$batch"; + + private static final String REQUEST_BODY = + readResourceFileCrlf(ODataV4BatchRequestUnitTest.class, "BatchReadsAndWritesSuccessRequest.txt"); + private static final String RESPONSE_BODY = + readResourceFileCrlf(ODataV4BatchRequestUnitTest.class, "BatchReadsAndWritesSuccessResponse.txt"); + + private static final TestEntity ENTITY_CREATE = new TestEntity(); + private static final TestEntity ENTITY_UPDATE = TestEntity.builder().id("upd").build(); + private static final TestEntity ENTITY_DELETE = TestEntity.builder().id("del").build(); + + private static final GetAllRequestBuilder READ_ALL = SERVICE.getTestEntities(); + private static final GetByKeyRequestBuilder READ_BY_KEY = SERVICE.getTestEntitiesByKey("foobar"); + private static final CreateRequestBuilder CREATE = SERVICE.createTestEntity(ENTITY_CREATE); + private static final UpdateRequestBuilder UPDATE = SERVICE.updateTestEntity(ENTITY_UPDATE); + private static final DeleteRequestBuilder DELETE = SERVICE.deleteTestEntity(ENTITY_DELETE); + private static final SingleValueFunctionRequestBuilder FUNC_SINGLE = SERVICE.functionSingleResult(); + private static final CollectionValueFunctionRequestBuilder FUNC_MULTIPLE = SERVICE.functionMultipleResult(); + private static final SingleValueActionRequestBuilder ACT_SINGLE = SERVICE.actionSingleResult(); + private static final CollectionValueActionRequestBuilder ACT_MULTIPLE = SERVICE.actionMultipleResult(); + private static final int MAX_PARALLEL_CONNECTIONS = 10; + + private Destination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + // Mock CSRF token handling + + stubFor( + head(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_FETCH_VALUE)) + .willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + + // Mock OData Batch response + final String contentType = "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + stubFor(post(urlEqualTo(REQUEST_URL_BATCH)).willReturn(okForContentType(contentType, RESPONSE_BODY))); + + ApacheHttpClient5Accessor + .setHttpClientFactory( + new ApacheHttpClient5FactoryBuilder() + .maxConnectionsTotal(MAX_PARALLEL_CONNECTIONS) + .maxConnectionsPerRoute(MAX_PARALLEL_CONNECTIONS) + .build()); + } + + @AfterEach + void teardown() + { + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } + + @Test + void testAllOperations() + { + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + // execute batched requests + final BatchResponse response = + SERVICE + .batch() + .addReadOperations(READ_ALL, READ_BY_KEY) + .addChangeset(CREATE, UPDATE, DELETE) + .addReadOperations(FUNC_SINGLE, FUNC_MULTIPLE) + .addChangeset(ACT_SINGLE, ACT_MULTIPLE) + .execute(destination); + + // assertion on response parsing + final List readAllResult = response.getReadResult(READ_ALL); + assertThat(readAllResult).isNotEmpty(); + + final TestEntity readByKeyResult = response.getReadResult(READ_BY_KEY); + assertThat(readByKeyResult).isNotNull(); + + final ModificationResponse createResult = response.getModificationResult(CREATE); + assertThat(createResult).isNotNull(); + assertThat(createResult.getResponseEntity()).isNotNull().isNotEqualTo(ENTITY_CREATE); + assertThat(createResult.getResponseHeaders().get("Location")) + .containsExactly("https://localhost/service/TestEntities('new')"); + + final ModificationResponse updateResult = response.getModificationResult(UPDATE); + assertThat(updateResult).isNotNull(); + assertThat(updateResult.getResponseEntity()).isNotNull().isNotEqualTo(ENTITY_CREATE); + + final ModificationResponse deleteResult = response.getModificationResult(DELETE); + assertThat(readByKeyResult).isNotNull(); + assertThat(deleteResult.getResponseEntity()).isEmpty(); + + final Integer functionSingleResult = response.getReadResult(FUNC_SINGLE); + assertThat(functionSingleResult).isNotNull(); + + final List functionMultipleResult = response.getReadResult(FUNC_MULTIPLE); + assertThat(functionMultipleResult).isNotEmpty(); + + final ActionResponseSingle actionSingleResult = response.getModificationResult(ACT_SINGLE); + assertThat(actionSingleResult).isNotNull(); + assertThat(actionSingleResult.getResponseResult()).isNotEmpty(); + + final ActionResponseCollection actionMultipleResult = response.getModificationResult(ACT_MULTIPLE); + assertThat(actionMultipleResult).isNotNull(); + assertThat(actionMultipleResult.getResponseResult()).isNotEmpty(); + assertThat(actionMultipleResult.getResponseResult().get()).isNotEmpty(); + + } + // Verify request body + final String requestContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + verify( + postRequestedFor(urlEqualTo(REQUEST_URL_BATCH)) + .withHeader("Content-Type", equalTo(requestContentType)) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_VALUE)) + .withRequestBody(equalTo(REQUEST_BODY))); + + verify( + MAX_PARALLEL_CONNECTIONS * 2, + headRequestedFor(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_FETCH_VALUE))); + } + + @Test + void testIdenticalOperations() + { + final CreateRequestBuilder identicalCreate = SERVICE.createTestEntity(ENTITY_CREATE); + + final BatchResponse response = + SERVICE + .batch() + .addReadOperations(READ_ALL, READ_BY_KEY) + .addChangeset(CREATE, identicalCreate) + .execute(destination); + + final ModificationResponse result1 = response.getModificationResult(CREATE); + final ModificationResponse result2 = response.getModificationResult(identicalCreate); + + assertThat(result1.getModifiedEntity().getId()).isEqualTo("new"); + assertThat(result2.getModifiedEntity().getId()).isEqualTo("updated"); + + verify( + 1, + headRequestedFor(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_FETCH_VALUE))); + } + + @Test + void testLowLevelToHighLevel() + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); + + final BatchRequestBuilder batchBuilder = + SERVICE + .batch() + .addReadOperations(READ_ALL, READ_BY_KEY) + .addChangeset(CREATE, UPDATE, DELETE) + .addReadOperations(FUNC_SINGLE, FUNC_MULTIPLE) + .addChangeset(ACT_SINGLE, ACT_MULTIPLE); + + final ODataRequestBatch lowLevelRequest = batchBuilder.toRequest(); + + final ODataRequestResultMultipartGeneric lowLevelResult = lowLevelRequest.execute(httpClient); + + //Consumer transition from generic result object to typed result + final BatchResponse convertedHighLevelResult = BatchResponse.of(lowLevelResult, batchBuilder); + final BatchResponse expectedHighLevelResult = batchBuilder.execute(destination); + + assertThat(expectedHighLevelResult.getReadResult(READ_ALL)) + .isEqualTo(convertedHighLevelResult.getReadResult(READ_ALL)); + assertThat(expectedHighLevelResult.getReadResult(READ_BY_KEY)) + .isEqualTo(convertedHighLevelResult.getReadResult(READ_BY_KEY)); + assertThat(expectedHighLevelResult.getModificationResult(CREATE).getModifiedEntity()) + .isEqualTo(convertedHighLevelResult.getModificationResult(CREATE).getModifiedEntity()); + assertThat(expectedHighLevelResult.getModificationResult(CREATE).getResponseHeaders()) + .isEqualTo(convertedHighLevelResult.getModificationResult(CREATE).getResponseHeaders()); + assertThat(expectedHighLevelResult.getModificationResult(UPDATE).getModifiedEntity()) + .isEqualTo(convertedHighLevelResult.getModificationResult(UPDATE).getModifiedEntity()); + assertThat(expectedHighLevelResult.getModificationResult(UPDATE).getResponseHeaders()) + .isEqualTo(convertedHighLevelResult.getModificationResult(UPDATE).getResponseHeaders()); + assertThat(expectedHighLevelResult.getModificationResult(DELETE).getModifiedEntity()) + .isEqualTo(convertedHighLevelResult.getModificationResult(DELETE).getModifiedEntity()); + assertThat(expectedHighLevelResult.getModificationResult(DELETE).getResponseHeaders()) + .isEqualTo(convertedHighLevelResult.getModificationResult(DELETE).getResponseHeaders()); + assertThat(expectedHighLevelResult.getReadResult(FUNC_SINGLE)) + .isEqualTo(convertedHighLevelResult.getReadResult(FUNC_SINGLE)); + assertThat(expectedHighLevelResult.getReadResult(FUNC_MULTIPLE)) + .isEqualTo(convertedHighLevelResult.getReadResult(FUNC_MULTIPLE)); + assertThat(expectedHighLevelResult.getModificationResult(ACT_SINGLE).getResponseResult()) + .isEqualTo(convertedHighLevelResult.getModificationResult(ACT_SINGLE).getResponseResult()); + assertThat(expectedHighLevelResult.getModificationResult(ACT_SINGLE).getResponseHeaders()) + .isEqualTo(convertedHighLevelResult.getModificationResult(ACT_SINGLE).getResponseHeaders()); + assertThat(expectedHighLevelResult.getModificationResult(ACT_MULTIPLE).getResponseResult()) + .isEqualTo(convertedHighLevelResult.getModificationResult(ACT_MULTIPLE).getResponseResult()); + assertThat(expectedHighLevelResult.getModificationResult(ACT_MULTIPLE).getResponseHeaders()) + .isEqualTo(convertedHighLevelResult.getModificationResult(ACT_MULTIPLE).getResponseHeaders()); + } + + @Test + @SneakyThrows + void testBatchOpenConnection() + { + final int N = MAX_PARALLEL_CONNECTIONS * 2; + final List inputStreams = new ArrayList<>(); + + final HttpDestination dest = DefaultHttpDestination.builder("").build(); + final HttpClient httpClient = mock(HttpClient.class); + when(httpClient.executeOpen(isNull(), argThat(req -> req instanceof HttpPost), isNull())).thenAnswer(args -> { + final BasicClassicHttpResponse response = new BasicClassicHttpResponse(200, "ok"); + final InputStream inStream = mock(InputStream.class); + inputStreams.add(inStream); + response.setEntity(new InputStreamEntity(inStream, ContentType.APPLICATION_JSON)); + return response; + }); + + // configure test setup + ApacheHttpClient5Accessor.setHttpClientFactory(( anyDestination ) -> httpClient); + + // TEST: invoke many batch request each spawning an InputStream + for( int i = 0; i < N; i++ ) { + SERVICE.batch().addReadOperations(READ_ALL).execute(dest); + } + + // ASSERTION: input streams are loaded but never fully consumed + assertThat(inputStreams).hasSize(N); + for( final InputStream inStream : inputStreams ) { + Mockito.verify(inStream, never()).close(); + } + + // TEST: invoke one batch request using try-with-resource + try( BatchResponse result = SERVICE.batch().addReadOperations(READ_ALL).execute(dest) ) { + + assertThat(result).isNotNull(); + } + + // ASSERTION: input stream is fully consumed + Mockito.verify(inputStreams.get(N), times(1)).close(); + + // reset test setup + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODatav4BatchConnectionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODatav4BatchConnectionTest.java new file mode 100644 index 0000000000..ac3768fc7a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/ODatav4BatchConnectionTest.java @@ -0,0 +1,177 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.sap.cloud.sdk.datamodel.odatav4.TestUtility.readResourceFileCrlf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.ConnectionRequestTimeoutException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.github.tomakehurst.wiremock.matching.UrlPattern; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5FactoryBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataConnectionException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; + +@WireMockTest +class ODatav4BatchConnectionTest +{ + private static final String RESPONSE_CONTENT_TYPE = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + private static final String RESPONSE_WITH_CHANGESET = + readResourceFileCrlf(ODatav4BatchConnectionTest.class, "BatchResponseWithChangeset.txt"); + private static final String RESPONSE_WITHOUT_CHANGESET = + readResourceFileCrlf(ODatav4BatchConnectionTest.class, "BatchResponseWithoutChangeset.txt"); + private static final String RESPONSE_WITH_ERROR = + readResourceFileCrlf(ODatav4BatchConnectionTest.class, "BatchResponseWithError.txt"); + private static final TestEntityService SERVICE = new TestEntityService() + { + }; + private static final int MAX_PARALLEL_CONNECTIONS = 10; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + stubFor(head(UrlPattern.ANY).willReturn(noContent())); + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + ApacheHttpClient5Accessor + .setHttpClientFactory( + new ApacheHttpClient5FactoryBuilder() + .maxConnectionsTotal(MAX_PARALLEL_CONNECTIONS) + .maxConnectionsPerRoute(MAX_PARALLEL_CONNECTIONS) + .build()); + } + + @AfterEach + void teardown() + { + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } + + @Test + void testNoConnectionTimeoutWhenBatchResponseContainsNoChangeset() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITHOUT_CHANGESET))); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + final GetAllRequestBuilder READ_ALL = SERVICE.getTestEntities(); + final GetByKeyRequestBuilder READ_BY_KEY = SERVICE.getTestEntitiesByKey("foobar"); + final BatchResponse batchResponse = + SERVICE.batch().addReadOperations(READ_ALL, READ_BY_KEY).execute(destination); + + final List readAllResult = batchResponse.getReadResult(READ_ALL); + assertThat(readAllResult).isNotEmpty(); + + final TestEntity readByKeyResult = batchResponse.getReadResult(READ_BY_KEY); + assertThat(readByKeyResult).isNotNull(); + } + } + + @Test + void testNoConnectionTimeoutWhenBatchResponseContainsChangeset() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITH_CHANGESET))); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + final GetAllRequestBuilder READ_ALL = SERVICE.getTestEntities(); + final GetByKeyRequestBuilder READ_BY_KEY = SERVICE.getTestEntitiesByKey("foobar"); + final CreateRequestBuilder CREATE = SERVICE.createTestEntity(new TestEntity()); + final UpdateRequestBuilder UPDATE = + SERVICE.updateTestEntity(TestEntity.builder().id("upd").build()); + final DeleteRequestBuilder DELETE = + SERVICE.deleteTestEntity(TestEntity.builder().id("del").build()); + final BatchResponse batchResponse = + SERVICE + .batch() + .addReadOperations(READ_ALL, READ_BY_KEY) + .addChangeset(CREATE, UPDATE, DELETE) + .execute(destination); + + final List readAllResult = batchResponse.getReadResult(READ_ALL); + assertThat(readAllResult).isNotEmpty(); + + final TestEntity readByKeyResult = batchResponse.getReadResult(READ_BY_KEY); + assertThat(readByKeyResult).isNotNull(); + + final ModificationResponse createResult = batchResponse.getModificationResult(CREATE); + assertThat(createResult).isNotNull(); + + final ModificationResponse updateResult = batchResponse.getModificationResult(UPDATE); + assertThat(updateResult).isNotNull(); + assertThat(updateResult.getResponseEntity()).isNotNull(); + + final ModificationResponse deleteResult = batchResponse.getModificationResult(DELETE); + assertThat(deleteResult).isNotNull(); + assertThat(deleteResult.getResponseEntity()).isEmpty(); + } + } + + @Test + void testNoConnectionTimeoutWhenBatchResponseContainsError() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITH_ERROR))); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS * 2; i++ ) { + final GetAllRequestBuilder READ_ALL = SERVICE.getTestEntities(); + final GetByKeyRequestBuilder READ_BY_KEY = SERVICE.getTestEntitiesByKey("foobar"); + final DeleteRequestBuilder DELETE = + SERVICE.deleteTestEntity(TestEntity.builder().id("del").build()); + final BatchResponse batchResponse = + SERVICE.batch().addReadOperations(READ_ALL, READ_BY_KEY).addChangeset(DELETE).execute(destination); + + final List readAllResult = batchResponse.getReadResult(READ_ALL); + assertThat(readAllResult).isNotEmpty(); + + final TestEntity readByKeyResult = batchResponse.getReadResult(READ_BY_KEY); + assertThat(readByKeyResult).isNotNull(); + + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getModificationResult(DELETE)) + .satisfies(e -> assertThat(e.getHttpCode()).isEqualTo(400)); + } + } + + @Test + @Timeout( value = 300_000L, unit = TimeUnit.MILLISECONDS ) + @Disabled( "Test triggers a ConnectionPoolTimeoutException. Use it only to manually verify behaviour." ) + void testConnectionTimeoutWhenBatchResponseIsNotConsumedFully() + { + stubFor(post(UrlPattern.ANY).willReturn(okForContentType(RESPONSE_CONTENT_TYPE, RESPONSE_WITHOUT_CHANGESET))); + + final GetAllRequestBuilder READ_ALL = SERVICE.getTestEntities(); + final GetByKeyRequestBuilder READ_BY_KEY = SERVICE.getTestEntitiesByKey("foobar"); + + for( int i = 0; i < MAX_PARALLEL_CONNECTIONS; ++i ) { + assertThatNoException() + .isThrownBy(() -> SERVICE.batch().addReadOperations(READ_ALL, READ_BY_KEY).execute(destination)); + } + + assertThatThrownBy(() -> SERVICE.batch().addReadOperations(READ_ALL, READ_BY_KEY).execute(destination)) + .isInstanceOf(ODataConnectionException.class) + .hasRootCauseExactlyInstanceOf(ConnectionRequestTimeoutException.class) + .hasMessageContaining( + "Please execute your request with try-with-resources to ensure resources are properly closed."); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderEtagParsingTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderEtagParsingTest.java new file mode 100644 index 0000000000..7184b8ddc2 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/RequestBuilderEtagParsingTest.java @@ -0,0 +1,371 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5CacheBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestinationProperties; + +import lombok.Getter; +import lombok.SneakyThrows; + +class RequestBuilderEtagParsingTest +{ + private static final String SERVICE_PATH = "/service"; + private static final String ENTITY_COLLECTION = "/EntityCollection"; + private static final HttpDestinationProperties DESTINATION = DefaultHttpDestination.builder("http://1").build(); + private static final String FUNCTION_NAME = "FUNCTION_NAME"; + private static final String ETAG_HEAD = "foo"; + private static final String ETAG_BODY = "bar"; + private static final Map HEADERS_NONE = Collections.emptyMap(); + private static final Map HEADERS_WITH_ETAG = Collections.singletonMap("Etag", ETAG_HEAD); + + private HttpClient httpClient; + + @BeforeEach + void setupConnectivity() + { + httpClient = mock(HttpClient.class); + ApacheHttpClient5Accessor + .setHttpClientCache(new ApacheHttpClient5CacheBuilder().durationInMilliseconds(0).build()); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> { + assertThat(dest).isSameAs(DESTINATION); + return httpClient; + }); + } + + @AfterEach + void teardownConnectivity() + { + ApacheHttpClient5Accessor + .setHttpClientCache(new ApacheHttpClient5CacheBuilder().duration(Duration.ofMinutes(5)).build()); + ApacheHttpClient5Accessor.setHttpClientFactory(null); + } + + /** + * ETag via HTTP header. + */ + @SneakyThrows + @Test + void testParseNoEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{}", HEADERS_NONE)); + + final TestEntity entity = + new GetByKeyRequestBuilder<>( + SERVICE_PATH, + TestEntity.class, + Collections.singletonMap("key", "val"), + ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseNoEtagFunctionAction() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(String.format("{%s:{}}", FUNCTION_NAME), HEADERS_NONE)); + + // GET + TestEntity entity = + new SingleValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + + // POST + entity = + new SingleValueActionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class) + .execute(DESTINATION) + .getResponseResult() + .get(); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseNoEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{}", HEADERS_NONE)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + new UpdateRequestBuilder<>(SERVICE_PATH, requestEntity, ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).isEmpty(); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).isEmpty(); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + @SneakyThrows + @Test + void testParseHeaderEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{}", HEADERS_WITH_ETAG)); + + final TestEntity entity = + new GetByKeyRequestBuilder<>( + SERVICE_PATH, + TestEntity.class, + Collections.singletonMap("key", "val"), + ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderEtagFunctionAction() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{%s:{}}", FUNCTION_NAME), HEADERS_WITH_ETAG)); + + // GET + TestEntity entity = + new SingleValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + + // POST + entity = + new SingleValueActionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class) + .execute(DESTINATION) + .getResponseResult() + .get(); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response("{}", HEADERS_WITH_ETAG)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + new UpdateRequestBuilder<>(SERVICE_PATH, requestEntity, ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * ETag via HTTP payload. + */ + @SneakyThrows + @Test + void testParsePayloadEtagGetAll() + { + final String payload = String.format("{value:[{@etag:\"%s\"}]}", ETAG_BODY); + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(payload, HEADERS_NONE)); + + final List entities = + new GetAllRequestBuilder<>(SERVICE_PATH, TestEntity.class, ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(entities).isNotNull().hasSize(1); + assertThat(entities.get(0).getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(String.format("{@etag:\"%s\"}", ETAG_BODY), HEADERS_NONE)); + + final TestEntity entity = + new GetByKeyRequestBuilder<>( + SERVICE_PATH, + TestEntity.class, + Collections.singletonMap("key", "val"), + ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagFunctionAction() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(String.format("{@etag:\"%s\"}", ETAG_BODY), HEADERS_NONE)); + + // GET + TestEntity entity = + new SingleValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + + // POST + entity = + new SingleValueActionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class) + .execute(DESTINATION) + .getResponseResult() + .get(); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_BODY); + } + + @SneakyThrows + @Test + void testParsePayloadEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())).thenReturn(response(String.format("{@etag:\"%s\"}", ETAG_BODY), HEADERS_NONE)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + new UpdateRequestBuilder<>(SERVICE_PATH, requestEntity, ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_BODY); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_BODY); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * ETag via HTTP header + HTTP payload. + */ + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagGetByKey() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{@etag:\"%s\"}", ETAG_BODY), HEADERS_WITH_ETAG)); + + final TestEntity entity = + new GetByKeyRequestBuilder<>( + SERVICE_PATH, + TestEntity.class, + Collections.singletonMap("key", "val"), + ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagFunctionAction() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{%s:{@etag:\"%s\"}}", FUNCTION_NAME, ETAG_BODY), HEADERS_WITH_ETAG)); + + // GET + TestEntity entity = + new SingleValueFunctionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class).execute(DESTINATION); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + + // POST + entity = + new SingleValueActionRequestBuilder<>(SERVICE_PATH, FUNCTION_NAME, TestEntity.class) + .execute(DESTINATION) + .getResponseResult() + .get(); + + assertThat(entity).isNotNull(); + assertThat(entity.getVersionIdentifier()).containsExactly(ETAG_HEAD); + } + + @SneakyThrows + @Test + void testParseHeaderAndPayloadEtagUpdate() + { + when(httpClient.executeOpen(isNull(), any(), isNull())) + .thenReturn(response(String.format("{@etag:\"%s\"}", ETAG_BODY), HEADERS_WITH_ETAG)); + + final TestEntity requestEntity = new TestEntity(); + + final ModificationResponse result = + new UpdateRequestBuilder<>(SERVICE_PATH, requestEntity, ENTITY_COLLECTION).execute(DESTINATION); + + assertThat(result).isNotNull(); + assertThat(result.getModifiedEntity()).isNotNull(); + assertThat(result.getResponseEntity()).isNotNull().isNotEmpty(); + assertThat(result.getRequestEntity()).isNotNull().isSameAs(requestEntity); + + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getResponseEntity().get().getVersionIdentifier()).containsExactly(ETAG_HEAD); + assertThat(result.getRequestEntity().getVersionIdentifier()).isEmpty(); + } + + /** + * HELPER METHODS. + */ + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String entityCollection = ENTITY_COLLECTION; + @Getter + private final Class type = TestEntity.class; + @Getter + private final String odataType = "Example.TestEntity"; + } + + @Nonnull + static ClassicHttpResponse response( @Nonnull final String payload, @Nonnull final Map headers ) + { + final BasicClassicHttpResponse result = new BasicClassicHttpResponse(200, "Ok"); + result.setEntity(new StringEntity(payload, StandardCharsets.UTF_8)); + result.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + headers.forEach(result::setHeader); + return result; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilderTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilderTest.java new file mode 100644 index 0000000000..ec7ea3ff62 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueActionRequestBuilderTest.java @@ -0,0 +1,316 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.TestUtility; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@WireMockTest +class SingleValueActionRequestBuilderTest +{ + private static final String DEFAULT_SERVICE_PATH = "/odata/default"; + private static final String ODATA_ACTION = "TestAction"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + stubFor(head(anyUrl()).willReturn(ok())); + } + + private static String readResourceFile( final String resourceFileName ) + { + return TestUtility.readResourceFile(SingleValueActionRequestBuilderTest.class, resourceFileName); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "com.sap.cloud.sdk.TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = TestEntity.class; + + @Override + protected String getDefaultServicePath() + { + return DEFAULT_SERVICE_PATH; + } + + @ElementName( "Name" ) + private String name; + } + + @Builder + @Data + @NoArgsConstructor + @AllArgsConstructor + @ToString( doNotUseGetters = true, callSuper = true ) + @EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class ComplexType extends VdmComplex + { + + @Getter + private final String odataType = "com.sap.cloud.sdk.ComplexType"; + + @Getter + private final Class type = ComplexType.class; + + @ElementName( "City" ) + private String city; + + @ElementName( "Country" ) + private String country; + } + + @Test + void testFunctionQueryWithParametersAndNoResponse() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionParametersRequestBody.json"); + + stubFor(post(urlPathEqualTo(actionRequestUrl)).willReturn(noContent())); + + final Map actionParameters = new LinkedHashMap<>(); + actionParameters.put("stringParameter", "test"); + actionParameters.put("booleanParameter", true); + actionParameters.put("integerParameter", 9000); + actionParameters.put("decimalParameter", 3.14d); + actionParameters.put("durationParameter", Duration.ofHours(8)); + actionParameters + .put("dateTimeOffsetParameter", OffsetDateTime.of(2020, 3, 12, 5, 2, 30, 0, ZoneOffset.of("Z"))); + actionParameters.put("timeOfDayParameter", LocalTime.of(13, 03, 39, 999000000)); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, actionParameters, Void.class); + assertThat(sut.toRequest().getRelativeUri()).hasToString(actionRequestUrl); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + + .withHeader("Content-Type", equalTo("application/json")) + + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult()).isEmpty(); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(204); + } + + @Test + void testActionWithNoParameterAndPrimitiveResponse() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + + stubFor(post(urlPathEqualTo(actionRequestUrl)).willReturn(okJson("{" + "\"value\" : 3.14" + "}"))); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, Float.class); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(actionRequestUrl)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).isEqualTo(3.14f); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithNoParameterAndStringResponse() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionNoParametersRequestBody.json"); + + stubFor(post(urlPathEqualTo(actionRequestUrl)).willReturn(okJson("{" + "\"value\" : \"Works\"" + "}"))); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_ACTION, String.class); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_ACTION)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get()).isEqualTo("Works"); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + + } + + @Test + void testActionWithEntityParameterAndEntityResponse() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionEntityRequestBody.json"); + + stubFor(post(urlPathEqualTo(actionRequestUrl)).willReturn(okJson("{ \"Name\" : \"Tester\" }"))); + + final Map actionParameters = new LinkedHashMap<>(); + final TestEntity testEntity = new TestEntity("Tester"); + actionParameters.put("entityParameter", testEntity); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>( + DEFAULT_SERVICE_PATH, + ODATA_ACTION, + actionParameters, + TestEntity.class); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(actionRequestUrl)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get().getName()).isEqualTo("Tester"); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithEntityParameterNull() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionNullEntityRequestBody.json"); + + stubFor(post(urlPathEqualTo(actionRequestUrl)).willReturn(okJson("{ \"Name\" : \"Tester\" }"))); + + final Map actionParameters = new LinkedHashMap<>(); + actionParameters.put("entityParameter", null); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>( + DEFAULT_SERVICE_PATH, + ODATA_ACTION, + actionParameters, + TestEntity.class); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(actionRequestUrl)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get().getName()).isEqualTo("Tester"); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } + + @Test + void testActionWithComplexTypeParameterComplexTypeResponse() + { + final String actionRequestUrl = String.format("%s/%s", DEFAULT_SERVICE_PATH, ODATA_ACTION); + final String actionRequestBody = readResourceFile("ActionComplexTypeRequestBody.json"); + + stubFor( + post(urlPathEqualTo(actionRequestUrl)) + .willReturn(okJson("{\"City\": \"Stockholm\",\"Country\": \"Sweden\"}"))); + + final Map actionParameters = new LinkedHashMap<>(); + final ComplexType testComplexType = new ComplexType("Stockholm", "Sweden"); + actionParameters.put("complexEntity", testComplexType); + + final SingleValueActionRequestBuilder sut = + new SingleValueActionRequestBuilder<>( + DEFAULT_SERVICE_PATH, + ODATA_ACTION, + actionParameters, + ComplexType.class); + final ActionResponseSingle actualResponse = sut.execute(destination); + + verify( + postRequestedFor(urlEqualTo(actionRequestUrl)) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(actionRequestBody))); + + verify( + 1, + headRequestedFor(urlEqualTo(DEFAULT_SERVICE_PATH + "/")).withHeader("x-csrf-token", equalTo("fetch"))); + + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseResult().get().getCity()).isEqualTo("Stockholm"); + assertThat(actualResponse.getResponseResult().get().getCountry()).isEqualTo("Sweden"); + assertThat(actualResponse.getResponseStatusCode()).isEqualTo(200); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilderTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilderTest.java new file mode 100644 index 0000000000..64419a3c6a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/SingleValueFunctionRequestBuilderTest.java @@ -0,0 +1,192 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataFunctionParameters; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@WireMockTest +class SingleValueFunctionRequestBuilderTest +{ + private static final String DEFAULT_SERVICE_PATH = "/odata/default"; + private static final String ODATA_FUNCTION = "TestFunction"; + private static final ODataFunctionParameters FUNCTION_PARAMETERS; + private static final Map FUNCTION_PARAMETER_MAP = new HashMap<>(); + + static { + FUNCTION_PARAMETER_MAP.put("stringParameter", "test"); + FUNCTION_PARAMETER_MAP.put("booleanParameter", true); + FUNCTION_PARAMETER_MAP.put("integerParameter", 9000); + FUNCTION_PARAMETER_MAP.put("decimalParameter", 3.14); + FUNCTION_PARAMETER_MAP.put("durationParameter", Duration.ofHours(8)); + FUNCTION_PARAMETER_MAP.put("dateTimeParameter", LocalDateTime.of(2019, 12, 25, 8, 0, 0)); + + FUNCTION_PARAMETERS = ODataFunctionParameters.of(FUNCTION_PARAMETER_MAP, ODataProtocol.V4); + } + + private static final String ODATA_FUNCTION_PARAMETER_WITH_SPECIAL_CHARACTER = "(stringParameter='t''est')"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Data + @EqualsAndHashCode( callSuper = true ) + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + @JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) + public static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "com.sap.cloud.sdk.TestEntity"; + + @Getter + private final String entityCollection = "EntityCollection"; + + @Getter + private final Class type = TestEntity.class; + + @Override + protected String getDefaultServicePath() + { + return DEFAULT_SERVICE_PATH; + } + + @ElementName( "Name" ) + @SerializedName( "Name" ) + @JsonProperty( "Name" ) + private String name; + } + + @Test + void testFunctionQueryWithoutParameters() + { + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_FUNCTION, Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + "()"); + } + + @Test + void testFunctionQueryWithMethodParameters() + { + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>( + DEFAULT_SERVICE_PATH, + ODATA_FUNCTION, + FUNCTION_PARAMETER_MAP, + Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + FUNCTION_PARAMETERS.toEncodedString()); + } + + @Test + void testFunctionQueryWithSpecialCharactersInMethodParameters() + { + final ODataFunctionParameters parameters = + new ODataFunctionParameters(ODataProtocol.V4).addParameter("stringParameter", "t'est"); + final ODataResourcePath functionPath = ODataResourcePath.of(ODATA_FUNCTION, parameters); + + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>(DEFAULT_SERVICE_PATH, functionPath, Void.class); + + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + ODATA_FUNCTION_PARAMETER_WITH_SPECIAL_CHARACTER); + } + + @Test + void testFunctionQueryWithMapParameters() + { + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>( + DEFAULT_SERVICE_PATH, + ODATA_FUNCTION, + FUNCTION_PARAMETER_MAP, + Void.class); + assertThat(requestBuilder.toRequest().getRelativeUri()) + .hasToString(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + FUNCTION_PARAMETERS.toEncodedString()); + } + + @Test + void testFunctionWithPrimitiveResponse() + { + stubFor( + get(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + "()")) + .willReturn(okJson("{" + "\"value\" : 3.14" + "}"))); + + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_FUNCTION, Float.class); + + final Float actualResponse = requestBuilder.execute(destination); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse).isEqualTo(3.14f); + } + + @Test + void testFunctionWithStringResponse() + { + stubFor( + get(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + "()")) + .willReturn(okJson("{" + "\"value\" : \"Works\"" + "}"))); + + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_FUNCTION, String.class); + + final String actualResponse = requestBuilder.execute(destination); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse).isEqualTo("Works"); + } + + @Test + void testFunctionWithEntityResponse() + { + stubFor( + get(urlPathEqualTo(DEFAULT_SERVICE_PATH + '/' + ODATA_FUNCTION + "()")) + .willReturn(okJson("{" + "\"Name\" : \"Tester\"" + "}"))); + + final SingleValueFunctionRequestBuilder requestBuilder = + new SingleValueFunctionRequestBuilder<>(DEFAULT_SERVICE_PATH, ODATA_FUNCTION, TestEntity.class); + + final TestEntity actualResponse = requestBuilder.execute(destination); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getName()).isEqualTo("Tester"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntity.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntity.java new file mode 100644 index 0000000000..6510520482 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntity.java @@ -0,0 +1,86 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class TestEntity extends VdmEntity +{ + static final String SERVICE_PATH = "/odata/default"; + + @Getter + private final String odataType = "TestEntity"; + + @Nonnull + @Override + protected String getEntityCollection() + { + return "EntityCollection"; + } + + @Nonnull + @Override + public Class getType() + { + return TestEntity.class; + } + + @Override + protected String getDefaultServicePath() + { + return SERVICE_PATH; + } + + @Nullable + @ElementName( "id" ) + private String id; + + public final static SimpleProperty.String ID = new SimpleProperty.String<>(TestEntity.class, "id"); + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey key = super.getKey(); + key.addKeyProperty("id", getId()); + return key; + } + + public void setId( @Nullable final String id ) + { + rememberChangedField("id", this.id); + this.id = id; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + return ImmutableMap. builder().putAll(super.toMapOfFields()).put("id", getId()).build(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntityService.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntityService.java new file mode 100644 index 0000000000..c297bdf5a4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/TestEntityService.java @@ -0,0 +1,108 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + +import com.google.common.collect.ImmutableMap; + +interface TestEntityService +{ + String SERVICE_PATH = "/"; + + @Nonnull + default GetAllRequestBuilder getTestEntities() + { + return new GetAllRequestBuilder<>(SERVICE_PATH, TestEntity.class, "EntityCollection"); + } + + @Nonnull + default CountRequestBuilder countTestEntities() + { + return new CountRequestBuilder<>(SERVICE_PATH, TestEntity.class, "EntityCollection"); + } + + @Nonnull + default GetByKeyRequestBuilder getTestEntitiesByKey( @Nonnull final String key ) + { + return new GetByKeyRequestBuilder<>( + SERVICE_PATH, + TestEntity.class, + ImmutableMap.of("key", key), + "EntityCollection"); + } + + @Nonnull + default CreateRequestBuilder createTestEntity( @Nonnull final TestEntity testEntity ) + { + return new CreateRequestBuilder<>(SERVICE_PATH, testEntity, "EntityCollection"); + } + + @Nonnull + default DeleteRequestBuilder deleteTestEntity( @Nonnull final TestEntity testEntity ) + { + return new DeleteRequestBuilder<>(SERVICE_PATH, testEntity, "EntityCollection"); + } + + @Nonnull + default UpdateRequestBuilder updateTestEntity( @Nonnull final TestEntity testEntity ) + { + return new UpdateRequestBuilder<>(SERVICE_PATH, testEntity, "EntityCollection"); + } + + @Nonnull + default SingleValueActionRequestBuilder actionSingleResult() + { + return new SingleValueActionRequestBuilder<>( + SERVICE_PATH, + "action-single", + ImmutableMap.of("secret", "pass"), + Integer.class); + } + + @Nonnull + default CollectionValueActionRequestBuilder actionMultipleResult() + { + return new CollectionValueActionRequestBuilder<>( + SERVICE_PATH, + "action-multiple", + ImmutableMap.of("secret", "pass"), + String.class); + } + + @Nonnull + default SingleValueFunctionRequestBuilder functionSingleResult() + { + return new SingleValueFunctionRequestBuilder<>( + SERVICE_PATH, + "function-single", + ImmutableMap.of("secret", "pass"), + Integer.class); + } + + @Nonnull + default CollectionValueFunctionRequestBuilder functionMultipleResult() + { + return new CollectionValueFunctionRequestBuilder<>( + SERVICE_PATH, + "function-multiple", + ImmutableMap.of("secret", "pass"), + String.class); + } + + @Nonnull + default BatchRequestBuilder batch() + { + final AtomicInteger uuidCounter = new AtomicInteger(); + return new BatchRequestBuilder(SERVICE_PATH) + { + @Override + protected Supplier getUuidProvider() + { + return () -> new UUID(0, uuidCounter.incrementAndGet()); + } + }; + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatchTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatchTest.java new file mode 100644 index 0000000000..e69b2a8294 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPatchTest.java @@ -0,0 +1,633 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +class UpdateRequestHelperPatchTest +{ + private static final String TEST_DEFAULT_SERVICE_PATH = "/odata/default"; + + @NoArgsConstructor + @AllArgsConstructor + @Builder + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + private static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "TestEntity"; + + @Getter + private final String entityCollection = "EntityParentCollection"; + + @Getter + private final String defaultServicePath = TEST_DEFAULT_SERVICE_PATH; + + @Getter + private final Class type = TestEntity.class; + + @Getter + @ElementName( "Neighbor" ) + private TestEntity neighbor; + + @Getter + @ElementName( "Value" ) + private BigDecimal value; + + @Getter + @ElementName( "Emails" ) + private List emails; + + @Getter + @ElementName( "Name" ) + private String name; + + @Getter + @ElementName( "EffortValue" ) + private Double effortValue; + + @Getter + @ElementName( "ShoeSize" ) + private Integer shoeSize; + + @Getter + @ElementName( "IsRetired" ) + private Boolean isRetired; + + @Getter + @ElementName( "BirthDate" ) + private LocalDate birthDate; + + @Getter + @ElementName( "FavouriteCharacter" ) + private Character favouriteCharacter; + + @Getter + @ElementName( "ComplexProperty" ) + private TestComplex complexProperty; + + @Getter + @ElementName( "ComplexProperties" ) + private Collection complexProperties; + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey key = new ODataEntityKey(ODataProtocol.V4); + key.addKeyProperty("Name", name); + return key; + } + + public void setNeighbor( TestEntity neighbor ) + { + rememberChangedField("Neighbor", this.neighbor); + this.neighbor = neighbor; + } + + public void setName( String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + public void setEffortValue( Double effortValue ) + { + rememberChangedField("EffortValue", this.effortValue); + this.effortValue = effortValue; + } + + public void setValue( BigDecimal value ) + { + rememberChangedField("Value", this.value); + this.value = value; + } + + public void setEmails( List emails ) + { + rememberChangedField("Emails", this.emails); + this.emails = emails; + } + + public void setShoeSize( Integer shoeSize ) + { + rememberChangedField("ShoeSize", this.shoeSize); + this.shoeSize = shoeSize; + } + + public void setIsRetired( Boolean isRetired ) + { + rememberChangedField("IsRetired", this.isRetired); + this.isRetired = isRetired; + } + + public void setBirthDate( LocalDate birthDate ) + { + rememberChangedField("BirthDate", this.birthDate); + this.birthDate = birthDate; + } + + public void setFavouriteCharacter( Character favouriteCharacter ) + { + rememberChangedField("FavouriteCharacter", this.favouriteCharacter); + this.favouriteCharacter = favouriteCharacter; + } + + public void setComplexProperty( TestComplex complexProperty ) + { + rememberChangedField("ComplexProperty", this.complexProperty); + this.complexProperty = complexProperty; + } + + public void setComplexProperties( Collection complexProperties ) + { + rememberChangedField("ComplexProperties", this.complexProperties); + this.complexProperties = complexProperties; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map result = new HashMap<>(); + result.put("Name", name); + result.put("Value", value); + result.put("Neighbor", neighbor); + result.put("Emails", emails); + result.put("EffortValue", effortValue); + result.put("ShoeSize", shoeSize); + result.put("IsRetired", isRetired); + result.put("BirthDate", birthDate); + result.put("FavouriteCharacter", favouriteCharacter); + result.put("ComplexProperty", complexProperty); + result.put("ComplexProperties", complexProperties); + return result; + } + } + + @Builder + @NoArgsConstructor + @AllArgsConstructor + private static class TestComplex extends VdmComplex + { + @Getter + private final String odataType = "TestComplex"; + + @Getter + @ElementName( "StringProperty" ) + private String stringProperty; + + @Getter + @ElementName( "ComplexProperty" ) + private TestComplex complexProperty; + + @Nonnull + @Override + public Class getType() + { + return TestComplex.class; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("StringProperty", getStringProperty()); + values.put("ComplexProperty", getComplexProperty()); + return values; + } + + public void setStringProperty( String stringProperty ) + { + rememberChangedField("StringProperty", this.stringProperty); + this.stringProperty = stringProperty; + } + + public void setComplexProperty( TestComplex complexProperty ) + { + rememberChangedField("ComplexProperty", this.complexProperty); + this.complexProperty = complexProperty; + } + } + + @Test + void testSimpleBigDecimalPropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setValue(new BigDecimal("0.00000000001")); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"Value\":0.00000000001,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSimpleDoublePropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setEffortValue(42d); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"EffortValue\":42,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSimpleIntegerPropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setShoeSize(46); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"ShoeSize\":46,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSimpleStringPropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setName("Bar"); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"Name\":\"Bar\",\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSimpleLocalDatePropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setBirthDate(LocalDate.MIN); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"BirthDate\":\"-999999999-01-01\",\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testSimpleSingleCharacterDatePropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setFavouriteCharacter('A'); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"FavouriteCharacter\":\"A\",\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSimpleBooleanPropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setIsRetired(Boolean.FALSE); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"IsRetired\":false,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testDirectSimpleCollectionPropertyPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").build(); + + entity.setEmails(Lists.newArrayList("foo@sap.com")); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"Emails\":[\"foo@sap.com\"],\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Disabled( "Indirect changes on entity properties are not tracked. CLOUDECOSYSTEM-8217" ) + // @Test + void testIndirectCollectionPropertyViaGetPatchPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").emails(Lists.newArrayList()).build(); + + entity.getEmails().add("foo@sap.com"); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"Emails\":[\"foo@sap.com\"],\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testNavigationPropertyExistingPatchPayload() + throws Exception + { + final TestEntity entityFoo = TestEntity.builder().name("Foo").value(BigDecimal.ZERO).build(); + final TestEntity entityBar = TestEntity.builder().name("Bar").value(BigDecimal.ONE).build(); + + entityFoo.setNeighbor(entityBar); + + final String json = new UpdateRequestHelperPatch().toJson(entityFoo, Collections.emptySet()); + assertEquals( + "{\"Neighbor\":{\"@id\":\"EntityParentCollection('Bar')\"},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testNavigationPropertyNewPatchPayload() + throws Exception + { + final TestEntity entityFoo = TestEntity.builder().name("Foo").value(BigDecimal.ZERO).build(); + + final TestEntity entityBar = new TestEntity(); + entityBar.setName("Bar"); + entityBar.setValue(BigDecimal.ONE); + + entityFoo.setNeighbor(entityBar); + + final String json = new UpdateRequestHelperPatch().toJson(entityFoo, Collections.emptySet()); + + assertEquals( + "{\"Neighbor\":{\"Value\":1,\"Name\":\"Bar\"},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testNavigationPropertyChangePatchPayload() + throws Exception + { + final TestEntity entityFoo = TestEntity.builder().name("Foo").value(BigDecimal.ZERO).build(); + final TestEntity entityBar = TestEntity.builder().name("Bar").build(); + entityBar.setValue(BigDecimal.ONE); + entityFoo.setNeighbor(entityBar); + + final String json = new UpdateRequestHelperPatch().toJson(entityFoo, Collections.emptySet()); + + assertEquals( + "{\"Neighbor\":{\"Value\":1,\"@id\":\"EntityParentCollection('Bar')\"},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testNavigationPropertyCyclicPatchPayload() + throws Exception + { + final TestEntity entityBar = new TestEntity(); + entityBar.setName("Bar"); + + final TestEntity entityFoo = new TestEntity(); + entityFoo.setName("Foo"); + entityFoo.setNeighbor(entityBar); + entityBar.setNeighbor(entityFoo); + + final String json = new UpdateRequestHelperPatch().toJson(entityFoo, Collections.emptySet()); + assertEquals(""" + {\ + "Neighbor":{\ + "Neighbor":{"@id":"EntityParentCollection('Foo')"},\ + "Name":"Bar"\ + },\ + "Name":"Foo",\ + "@odata.type":"#TestEntity"\ + }\ + """, json, JSONCompareMode.LENIENT); + } + + @Test + void testSetComplexMember() + throws Exception + { + final TestComplex complex = TestComplex.builder().stringProperty("Foo").build(); + complex.setCustomField("customField", "customValue"); + + final TestEntity entity = TestEntity.builder().build(); + entity.setComplexProperty(complex); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperty\":{\"StringProperty\":\"Foo\",\"customField\":\"customValue\"},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testRemoveComplexMember() + throws Exception + { + final TestEntity entity = + TestEntity.builder().complexProperty(TestComplex.builder().stringProperty("Foo").build()).build(); + entity.setComplexProperty(null); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"ComplexProperty\":null,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testUpdateFieldOfComplexMember() + throws Exception + { + final TestEntity entity = TestEntity.builder().complexProperty(new TestComplex()).build(); + entity.getComplexProperty().setStringProperty("Foo"); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperty\":{\"StringProperty\":\"Foo\"},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testRemoveFieldOfComplexMember() + throws Exception + { + final TestComplex complex = TestComplex.builder().stringProperty("Foo").build(); + complex.setCustomField("customField", "customValue"); + final TestEntity entity = TestEntity.builder().complexProperty(complex).build(); + entity.getComplexProperty().setStringProperty(null); + entity.getComplexProperty().setCustomField("customField", null); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperty\":{\"StringProperty\":null,\"customField\":null},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testUpdateFieldOfNestedComplexMember() + throws Exception + { + final TestComplex complexChild = new TestComplex(); + final TestComplex complexParent = + TestComplex.builder().stringProperty("Parent").complexProperty(complexChild).build(); + final TestEntity entity = TestEntity.builder().complexProperty(complexParent).build(); + complexChild.setStringProperty("Hello, World!"); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperty\":{\"StringProperty\":\"Parent\",\"ComplexProperty\":{\"StringProperty\":\"Hello, World!\"}},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testRemoveFieldOfNestedComplexMember() + throws Exception + { + final TestComplex complexChild = TestComplex.builder().stringProperty("Child").build(); + final TestComplex complexParent = + TestComplex.builder().stringProperty("Parent").complexProperty(complexChild).build(); + final TestEntity entity = TestEntity.builder().complexProperty(complexParent).build(); + entity.getComplexProperty().getComplexProperty().setStringProperty(null); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperty\":{\"StringProperty\":\"Parent\",\"ComplexProperty\":{\"StringProperty\":null}},\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testSetNullableCollectionToEmpty() + throws Exception + { + final TestEntity entity = TestEntity.builder().build(); + assertThat(entity.getComplexProperties()).isNull(); + entity.setComplexProperties(new ArrayList<>()); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"ComplexProperties\":[],\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSetEmptyToCollectionToEmpty() + throws Exception + { + final TestEntity entity = TestEntity.builder().complexProperties(new ArrayList<>()).build(); + entity.setComplexProperties(new ArrayList<>()); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testRemoveCollection() + throws Exception + { + final TestEntity entity = TestEntity.builder().complexProperties(new ArrayList<>()).build(); + entity.setComplexProperties(null); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"ComplexProperties\":null,\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } + + @Test + void testSetCollectionOfComplexProperties() + throws Exception + { + final TestComplex complex = TestComplex.builder().stringProperty("Foo").build(); + complex.setCustomField("customField", "customValue"); + + final TestEntity entity = TestEntity.builder().complexProperties(new ArrayList<>()).build(); + entity.setComplexProperties(Collections.singletonList(complex)); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperties\":[{\"StringProperty\":\"Foo\",\"ComplexProperty\":null,\"customField\":\"customValue\"}],\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testUpdateComplexMemberInCollection() + throws Exception + { + final TestComplex collectionMember = new TestComplex(); + final TestEntity entity = TestEntity.builder().complexProperties(Lists.newArrayList(collectionMember)).build(); + collectionMember.setStringProperty("Foo"); + collectionMember.setCustomField("customField", "customValue"); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperties\":[{\"StringProperty\":\"Foo\",\"customField\":\"customValue\"}],\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testRemoveFieldOfComplexMemberInCollection() + throws Exception + { + final TestComplex collectionMember = TestComplex.builder().stringProperty("Foo").build(); + final TestEntity entity = TestEntity.builder().complexProperties(Lists.newArrayList(collectionMember)).build(); + collectionMember.setStringProperty(null); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperties\":[{\"StringProperty\":null}],\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Disabled( "Tracking changes within a collection of complex properties is not yet supported. CLOUDECOSYSTEM-8217" ) + // @Test + void testAddMemberToComplexCollection() + throws Exception + { + final TestEntity entity = TestEntity.builder().complexProperties(new ArrayList<>()).build(); + entity.getComplexProperties().add(TestComplex.builder().stringProperty("Foo").build()); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals( + "{\"ComplexProperties\":[{\"StringProperty\":\"Foo\", \"ComplexProperty\":{}}],\"@odata.type\":\"#TestEntity\"}", + json, + JSONCompareMode.LENIENT); + } + + @Disabled( "Tracking changes within a collection of complex properties is not yet supported. CLOUDECOSYSTEM-8217" ) + // @Test + void testRemoveMemberFromComplexCollection() + throws Exception + { + final TestComplex collectionMember = TestComplex.builder().stringProperty("Foo").build(); + final TestEntity entity = + TestEntity.builder().complexProperties(Collections.singletonList(collectionMember)).build(); + entity.getComplexProperties().remove(collectionMember); + + final String json = new UpdateRequestHelperPatch().toJson(entity, Collections.emptySet()); + assertEquals("{\"ComplexProperties\":[],\"@odata.type\":\"#TestEntity\"}", json, JSONCompareMode.LENIENT); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPutTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPutTest.java new file mode 100644 index 0000000000..3f4762c80c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestHelperPutTest.java @@ -0,0 +1,159 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +class UpdateRequestHelperPutTest +{ + @NoArgsConstructor + @AllArgsConstructor + @Builder + @JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) + private static class TestEntity extends VdmEntity + { + @Getter + private final String odataType = "TestEntity"; + + @Getter + private final String entityCollection = "EntityParentCollection"; + + @Getter + private final Class type = TestEntity.class; + + @Getter + @ElementName( "Value" ) + private BigDecimal value; + + final static SimpleProperty.NumericDecimal VALUE = + new SimpleProperty.NumericDecimal<>(TestEntity.class, "Value"); + + @Getter + @ElementName( "Emails" ) + private List emails; + + final static SimpleProperty.Collection EMAILS = + new SimpleProperty.Collection<>(TestEntity.class, "Emails", String.class); + + @Getter + @ElementName( "Name" ) + private String name; + + final static SimpleProperty.String NAME = new SimpleProperty.String<>(TestEntity.class, "Name"); + + @Getter + @ElementName( "IsRetired" ) + private Boolean isRetired; + + final static SimpleProperty.Boolean IS_RETIRED = + new SimpleProperty.Boolean<>(TestEntity.class, "IsRetired"); + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey key = new ODataEntityKey(ODataProtocol.V4); + key.addKeyProperty("Name", name); + return key; + } + + public void setName( String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + public void setIsRetired( Boolean isRetired ) + { + rememberChangedField("IsRetired", this.isRetired); + this.isRetired = isRetired; + } + + public void setValue( BigDecimal value ) + { + rememberChangedField("Value", this.value); + this.value = value; + } + + public void setEmails( List emails ) + { + rememberChangedField("Emails", this.emails); + this.emails = emails; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map result = new HashMap<>(); + result.put("Name", name); + result.put("Value", value); + result.put("Emails", emails); + result.put("IsRetired", isRetired); + return result; + } + } + + @Test + void testPutPayload() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").isRetired(true).build(); + entity.setValue(new BigDecimal("0.00000000001")); + entity.setEmails(Collections.singletonList("sampleEmail@sap.com")); + final String json = new UpdateRequestHelperPut().toJson(entity, null); + + assertEquals( + "{\"@odata.type\":\"#TestEntity\",\"Value\":0.00000000001,\"Emails\":[\"sampleEmail@sap.com\"],\"Name\":\"Foo\",\"IsRetired\":true}", + json, + JSONCompareMode.LENIENT); + } + + @Test + void testPutPayloadWithExcludedFields() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").isRetired(true).build(); + entity.setValue(new BigDecimal("0.00000000001")); + final List excludedFields = + Arrays.asList(TestEntity.IS_RETIRED, TestEntity.NAME, TestEntity.EMAILS); + final String json = new UpdateRequestHelperPut().toJson(entity, excludedFields); + + assertEquals("{\"@odata.type\":\"#TestEntity\",\"Value\":0.00000000001}", json, JSONCompareMode.LENIENT); + } + + @Test + void testPutPayloadWithNullFields() + throws Exception + { + final TestEntity entity = TestEntity.builder().name("Foo").isRetired(true).build(); + entity.setValue(new BigDecimal("0.00000000001")); + final String json = new UpdateRequestHelperPut().toJson(entity, null); + + assertEquals( + "{\"@odata.type\":\"#TestEntity\",\"Value\":0.00000000001,\"Name\":\"Foo\",\"IsRetired\":true}", + json, + JSONCompareMode.STRICT); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityTest.java new file mode 100644 index 0000000000..df773b7e3b --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/VdmEntityTest.java @@ -0,0 +1,69 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import io.vavr.control.Option; + +class VdmEntityTest +{ + @Test + void testModifications() + { + final TestEntity entity = new TestEntity(); + assertThat(entity).isEqualTo(new TestEntity()); + assertThat(entity.getVersionIdentifier()).isEqualTo(Option.none()); + + entity.setCustomField("foo", "bar"); + + final SimpleProperty customField = () -> "fizz"; + entity.setCustomField(customField, "buzz"); + + assertThat(entity.getCustomFields()).containsEntry("foo", "bar").containsEntry("fizz", "buzz"); + assertThat(entity. getCustomField("foo")).isEqualTo("bar"); + assertThat(entity. getCustomField(customField)).isEqualTo("buzz"); + assertThat(entity.hasCustomField("fizz")).isTrue(); + assertThat(entity.hasCustomField(customField)).isTrue(); + assertThat(entity.getCustomFieldNames()).containsExactly("foo", "fizz"); + + entity.fromMap(Collections.singletonMap("foo", "barbar")); + assertThat(entity. getCustomField("foo")).isEqualTo("barbar"); + + assertThat(entity.toString()).isNotNull(); + } + + @Test + void testChangedNonCustomFields() + { + final TestEntity entity = TestEntity.builder().id("old").build(); + + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setId("old"); + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setId("new"); + assertThat(entity.getChangedFields()).containsOnlyKeys("id"); + } + + @Test + void testChangedCustomFields() + { + final TestEntity entity = TestEntity.builder().id("id").build(); + + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setCustomField("foo", "bar"); + assertThat(entity.getChangedFields()).containsOnlyKeys("foo"); + + entity.resetChangedFields(); + entity.setCustomField("foo", "bar"); + assertThat(entity.getChangedFields()).isEmpty(); + + entity.setCustomField("foo", "baz"); + assertThat(entity.getChangedFields()).containsOnlyKeys("foo"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableOperandsTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableOperandsTest.java new file mode 100644 index 0000000000..e0d40649f3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/expression/FilterableOperandsTest.java @@ -0,0 +1,575 @@ +package com.sap.cloud.sdk.datamodel.odatav4.expression; + +import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V4; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.time.Duration; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.expression.FilterExpression; +import com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmComplex; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +import lombok.Getter; + +class FilterableOperandsTest +{ + private static class TestEntity extends VdmEntity + { + private static final SimpleProperty.Boolean ALIVE = + new SimpleProperty.Boolean<>(TestEntity.class, "Alive"); + private static final SimpleProperty.Enum PROGRAMMING_LANGUAGE = + new SimpleProperty.Enum<>(TestEntity.class, "ProgrammingLanguage", "SoftwareEngineer.Programming.Language"); + + @Getter + private final String entityCollection = "TestEntities"; + @Getter + private final String odataType = "Odata.TestEntity"; + @Getter + private final Class type = TestEntity.class; + + enum Language implements VdmEnum + { + Java; + } + } + + private static class TestComputer extends VdmComplex + { + @Getter + private final String odataType = "Odata.TestComputer"; + @Getter + private final Class type = TestComputer.class; + } + + @Test + void testFilterableBoolean() + { + final SimpleProperty.Boolean f1 = new SimpleProperty.Boolean<>(TestEntity.class, "Field1"); + final String expression = f1.and(true).and(f1).not().or(true).or(f1).getExpression(V4); + assertThat(expression).isEqualTo("(((not ((Field1 and true) and Field1)) or true) or Field1)"); + } + + @Test + void testFilterableCollection() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.Collection telephone = + new SimpleProperty.Collection<>(TestEntity.class, "TelephoneNumber", Integer.class); + final SimpleProperty.Collection areaCode = + new SimpleProperty.Collection<>(TestEntity.class, "AreaCode", Integer.class); + final NavigationProperty.Collection friends = + new NavigationProperty.Collection<>(TestEntity.class, "Friends", TestEntity.class); + + { + final String expression = friends.all(TestEntity.ALIVE.equalTo(true)).getExpression(V4); + softly.assertThat(expression).isEqualTo("Friends/all(a:(a/Alive eq true))"); + } + { + final String expression = friends.any(TestEntity.ALIVE.equalTo(true)).getExpression(V4); + softly.assertThat(expression).isEqualTo("Friends/any(a:(a/Alive eq true))"); + } + { + final String expression = areaCode.concat(telephone).concat(Arrays.asList(4, 2)).getExpression(V4); + softly.assertThat(expression).isEqualTo("concat(concat(AreaCode,TelephoneNumber),[4,2])"); + } + { + final String expression = areaCode.substring(1).substring(2, 3).getExpression(V4); + softly.assertThat(expression).isEqualTo("substring(substring(AreaCode,1),2,3)"); + } + { + final String expression = telephone.contains(areaCode).getExpression(V4); + softly.assertThat(expression).isEqualTo("contains(TelephoneNumber,AreaCode)"); + } + { + final String expression = telephone.contains(Arrays.asList(0, 3, 0)).getExpression(V4); + softly.assertThat(expression).isEqualTo("contains(TelephoneNumber,[0,3,0])"); + } + { + final String expression = telephone.endsWith(Arrays.asList(4, 2)).getExpression(V4); + softly.assertThat(expression).isEqualTo("endswith(TelephoneNumber,[4,2])"); + } + { + final String expression = telephone.endsWith(telephone).getExpression(V4); + softly.assertThat(expression).isEqualTo("endswith(TelephoneNumber,TelephoneNumber)"); + } + { + final String expression = telephone.startsWith(Arrays.asList(0, 3, 0)).getExpression(V4); + softly.assertThat(expression).isEqualTo("startswith(TelephoneNumber,[0,3,0])"); + } + { + final String expression = telephone.startsWith(telephone).getExpression(V4); + softly.assertThat(expression).isEqualTo("startswith(TelephoneNumber,TelephoneNumber)"); + } + { + final String expression = telephone.hasSubSequence(areaCode).getExpression(V4); + softly.assertThat(expression).isEqualTo("hassubsequence(TelephoneNumber,AreaCode)"); + } + { + final String expression = telephone.hasSubSequence(Arrays.asList(0, 3, 0)).getExpression(V4); + softly.assertThat(expression).isEqualTo("hassubsequence(TelephoneNumber,[0,3,0])"); + } + { + final String expression = telephone.hasSubset(areaCode).getExpression(V4); + softly.assertThat(expression).isEqualTo("hassubset(TelephoneNumber,AreaCode)"); + } + { + final String expression = telephone.hasSubset(Arrays.asList(0, 3, 0)).getExpression(V4); + softly.assertThat(expression).isEqualTo("hassubset(TelephoneNumber,[0,3,0])"); + } + { + final String expression = telephone.indexOf(areaCode).getExpression(V4); + softly.assertThat(expression).isEqualTo("indexof(TelephoneNumber,AreaCode)"); + } + { + final String expression = telephone.indexOf(Arrays.asList(0, 3, 0)).getExpression(V4); + softly.assertThat(expression).isEqualTo("indexof(TelephoneNumber,[0,3,0])"); + } + { + final String expression = telephone.length().getExpression(V4); + softly.assertThat(expression).isEqualTo("length(TelephoneNumber)"); + } + softly.assertAll(); + } + + @Test + void testFilterableComplex() + { + final ComplexProperty.Single f1 = + new ComplexProperty.Single<>(TestEntity.class, "OperatingSystem", TestComputer.class); + final String expression1 = f1.has("Debian").getExpression(V4); + assertThat(expression1).isEqualTo("(OperatingSystem has 'Debian')"); + + final ComplexProperty.Single f2 = + new ComplexProperty.Single<>(TestEntity.class, "Apartment", TestComputer.class); + final SimpleProperty.Enum enumValue = + new SimpleProperty.Enum<>(TestEntity.class, "Animal", "OData.Test.AnimalType"); + final String expression2 = f2.has(enumValue).getExpression(V4); + assertThat(expression2).isEqualTo("(Apartment has Animal)"); + + assertThat(f1.equalToNull().getExpression(V4)).isEqualTo("(OperatingSystem eq null)"); + assertThat(f1.notEqualToNull().getExpression(V4)).isEqualTo("(OperatingSystem ne null)"); + } + + @Test + void testFilterableTime() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.Time time = new SimpleProperty.Time<>(TestEntity.class, "Registration"); + softly.assertThat(time.timeFractionalSeconds().getExpression(V4)).isEqualTo("fractionalseconds(Registration)"); + softly.assertThat(time.timeSecond().getExpression(V4)).isEqualTo("second(Registration)"); + softly.assertThat(time.timeMinute().getExpression(V4)).isEqualTo("minute(Registration)"); + softly.assertThat(time.timeHour().getExpression(V4)).isEqualTo("hour(Registration)"); + softly.assertAll(); + } + + @Test + void testFilterableDate() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.Date date = new SimpleProperty.Date<>(TestEntity.class, "Registration"); + final SimpleProperty.Duration duration = + new SimpleProperty.Duration<>(TestEntity.class, "ResponseTime"); + { + final String expression = + date + .add(Duration.ofDays(1)) + .subtract(Duration.ofDays(2)) + .difference(LocalDate.of(2001, 1, 1)) + .getExpression(V4); + softly + .assertThat(expression) + .isEqualTo("(((Registration add duration'PT24H') sub duration'PT48H') sub 2001-01-01)"); + } + { + final String expression = date.add(duration).subtract(duration).difference(date).getExpression(V4); + softly + .assertThat(expression) + .isEqualTo("(((Registration add ResponseTime) sub ResponseTime) sub Registration)"); + } + softly.assertThat(date.dateDay().getExpression(V4)).isEqualTo("day(Registration)"); + softly.assertThat(date.dateMonth().getExpression(V4)).isEqualTo("month(Registration)"); + softly.assertThat(date.dateYear().getExpression(V4)).isEqualTo("year(Registration)"); + softly.assertAll(); + } + + @Test + void testFilterableDateTime() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.DateTime dt = new SimpleProperty.DateTime<>(TestEntity.class, "Registration"); + final SimpleProperty.Duration duration = + new SimpleProperty.Duration<>(TestEntity.class, "ResponseTime"); + + { + final String expression = dt.add(Duration.ofDays(1)).subtract(Duration.ofDays(2)).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Registration add duration'PT24H') sub duration'PT48H')"); + } + { + final String expression = dt.add(duration).subtract(duration).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Registration add ResponseTime) sub ResponseTime)"); + } + softly.assertThat(dt.date().getExpression(V4)).isEqualTo("date(Registration)"); + softly.assertThat(dt.time().getExpression(V4)).isEqualTo("time(Registration)"); + softly.assertThat(dt.timeFractionalSeconds().getExpression(V4)).isEqualTo("fractionalseconds(Registration)"); + softly.assertThat(dt.timeHour().getExpression(V4)).isEqualTo("hour(Registration)"); + softly.assertThat(dt.timeMinute().getExpression(V4)).isEqualTo("minute(Registration)"); + softly.assertThat(dt.timeSecond().getExpression(V4)).isEqualTo("second(Registration)"); + softly.assertThat(dt.dateDay().getExpression(V4)).isEqualTo("day(Registration)"); + softly.assertThat(dt.dateMonth().getExpression(V4)).isEqualTo("month(Registration)"); + softly.assertThat(dt.dateYear().getExpression(V4)).isEqualTo("year(Registration)"); + softly.assertThat(dt.offsetMinutes().getExpression(V4)).isEqualTo("totaloffsetminutes(Registration)"); + softly.assertAll(); + } + + @Test + void testFilterableDuration() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.Duration duration = + new SimpleProperty.Duration<>(TestEntity.class, "ResponseTime"); + final SimpleProperty.NumericDecimal number = + new SimpleProperty.NumericDecimal<>(TestEntity.class, "Size"); + { + final String expression = duration.add(duration).subtract(duration).negate().getExpression(V4); + softly.assertThat(expression).isEqualTo("-(((ResponseTime add ResponseTime) sub ResponseTime))"); + } + { + final String expression = duration.add(Duration.ofDays(1)).subtract(Duration.ofDays(2)).getExpression(V4); + softly.assertThat(expression).isEqualTo("((ResponseTime add duration'PT24H') sub duration'PT48H')"); + } + { + final String expression = duration.divide(2).multiply(3).getExpression(V4); + softly.assertThat(expression).isEqualTo("((ResponseTime div 2) mul 3)"); + } + { + final String expression = duration.divide(number).multiply(number).getExpression(V4); + softly.assertThat(expression).isEqualTo("((ResponseTime div Size) mul Size)"); + } + softly.assertThat(duration.offsetSeconds().getExpression(V4)).isEqualTo("totaloffsetseconds(ResponseTime)"); + softly.assertAll(); + } + + @Test + void testFilterableNumberApiInteger() + { + final SimpleProperty.NumericInteger number = + new SimpleProperty.NumericInteger<>(TestEntity.class, "Age"); + final SoftAssertions softly = new SoftAssertions(); + + // internal API tests + { + final FilterExpression delegate = mock(FilterExpression.class); + final FilterableNumericInteger.Expression expression = + new FilterableNumericInteger.Expression<>(delegate, Object.class); + softly.assertThat(expression.getDelegate()).isEqualTo(delegate); + softly.assertThat(expression.getEntityType()).isEqualTo(Object.class); + } + + // type safety for NumericInteger functions + Lists + ., FilterableNumeric>> newArrayList(FilterableNumericInteger::negate + + ) + .forEach(f -> softly.assertThat(f.apply(number)).isInstanceOf(FilterableNumericInteger.class)); + + // type safety for NumericInteger functions and Long argument + Lists + ., Long, FilterableNumeric>> newArrayList( + FilterableNumericInteger::add, + FilterableNumericInteger::subtract, + FilterableNumericInteger::multiply, + FilterableNumericInteger::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, 42L)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericInteger.class)); + + // type safety for NumericInteger functions and Integer argument + Lists + ., Integer, FilterableNumeric>> newArrayList( + FilterableNumericInteger::add, + FilterableNumericInteger::subtract, + FilterableNumericInteger::multiply, + FilterableNumericInteger::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, 42)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericInteger.class)); + + // type safety for NumericInteger functions and Decimal argument + Lists + ., Number, FilterableNumeric>> newArrayList( + FilterableNumericInteger::add, + FilterableNumericInteger::subtract, + FilterableNumericInteger::multiply, + FilterableNumericInteger::divide, + FilterableNumericInteger::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, Math.PI)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericDecimal.class)); + + softly.assertAll(); + } + + @Test + void testFilterableNumberApiDecimal() + { + final SimpleProperty.NumericDecimal number = + new SimpleProperty.NumericDecimal<>(TestEntity.class, "Size"); + final SoftAssertions softly = new SoftAssertions(); + + // internal API tests + { + final FilterExpression delegate = mock(FilterExpression.class); + final FilterableNumericDecimal.Expression expression = + new FilterableNumericDecimal.Expression<>(delegate, Object.class); + softly.assertThat(expression.getDelegate()).isEqualTo(delegate); + softly.assertThat(expression.getEntityType()).isEqualTo(Object.class); + } + + // type safety for NumericDecimal functions + Lists + ., FilterableNumeric>> newArrayList(FilterableNumericDecimal::negate + + ) + .forEach(f -> softly.assertThat(f.apply(number)).isInstanceOf(FilterableNumericDecimal.class)); + + // type safety for NumericDecimal functions + Lists + ., FilterableNumeric>> newArrayList( + FilterableNumericDecimal::ceil, + FilterableNumericDecimal::floor, + FilterableNumericDecimal::round + + ) + .forEach(f -> softly.assertThat(f.apply(number)).isInstanceOf(FilterableNumericInteger.class)); + + // type safety for NumericDecimal functions and Long argument + Lists + ., Long, FilterableNumeric>> newArrayList( + FilterableNumericDecimal::add, + FilterableNumericDecimal::subtract, + FilterableNumericDecimal::multiply, + FilterableNumericDecimal::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, 42L)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericDecimal.class)); + + // type safety for NumericDecimal functions and Integer argument + Lists + ., Integer, FilterableNumeric>> newArrayList( + FilterableNumericDecimal::add, + FilterableNumericDecimal::subtract, + FilterableNumericDecimal::multiply, + FilterableNumericDecimal::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, 42)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericDecimal.class)); + + // type safety for NumericDecimal functions and Decimal argument + Lists + ., Number, FilterableNumeric>> newArrayList( + FilterableNumericDecimal::add, + FilterableNumericDecimal::subtract, + FilterableNumericDecimal::multiply, + FilterableNumericDecimal::divide, + FilterableNumericDecimal::modulo) + .forEach( + f -> softly + .assertThat(f.apply(number, Math.PI)) + .describedAs(f.toString()) + .isInstanceOf(FilterableNumericDecimal.class)); + + softly.assertAll(); + } + + @Test + void testFilterableNumber() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.NumericInteger number = + new SimpleProperty.NumericInteger<>(TestEntity.class, "Age"); + final SimpleProperty.NumericDecimal size = + new SimpleProperty.NumericDecimal<>(TestEntity.class, "Size"); + { + final String expression = number.add(number).subtract(number).negate().getExpression(V4); + softly.assertThat(expression).isEqualTo("-(((Age add Age) sub Age))"); + } + { + final String expression = size.add(size).subtract(size).negate().getExpression(V4); + softly.assertThat(expression).isEqualTo("-(((Size add Size) sub Size))"); + } + { + final String expression = number.add(1).subtract(2).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Age add 1) sub 2)"); + } + { + final String expression = number.multiply(number).divide(number).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Age mul Age) divby Age)"); + } + { + final String expression = size.multiply(size).divide(size).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Size mul Size) divby Size)"); + } + { + final String expression = number.multiply(1).divide(2).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Age mul 1) divby 2)"); + } + { + final String expression = number.modulo(number).modulo(3).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Age mod Age) mod 3)"); + } + { + final String expression = size.modulo(size).modulo(3.0).getExpression(V4); + softly.assertThat(expression).isEqualTo("((Size mod Size) mod 3.0)"); + } + { + final String expression = size.in(42, 13.37, 9000f).getExpression(V4); + softly.assertThat(expression).isEqualTo("(Size in (42,13.37,9000.0))"); + } + { + softly.assertThat(size.ceil().getExpression(V4)).isEqualTo("ceiling(Size)"); + softly.assertThat(size.floor().getExpression(V4)).isEqualTo("floor(Size)"); + softly.assertThat(size.round().getExpression(V4)).isEqualTo("round(Size)"); + } + { + final String expression = + number + .equalTo(number) + .and(number.equalTo(42)) + .and(number.notEqualTo(number)) + .and(number.notEqualTo(42)) + .getExpression(V4); + softly + .assertThat(expression) + .isEqualTo("((((Age eq Age) and (Age eq 42)) and (Age ne Age)) and (Age ne 42))"); + } + { + final String expression = + number + .greaterThan(number) + .and(number.greaterThan(42)) + .and(number.greaterThanEqual(number)) + .and(number.greaterThanEqual(42)) + .getExpression(V4); + softly + .assertThat(expression) + .isEqualTo("((((Age gt Age) and (Age gt 42)) and (Age ge Age)) and (Age ge 42))"); + } + { + final String expression = + number + .lessThan(number) + .and(number.lessThan(42)) + .and(number.lessThanEqual(number)) + .and(number.lessThanEqual(42)) + .getExpression(V4); + softly + .assertThat(expression) + .isEqualTo("((((Age lt Age) and (Age lt 42)) and (Age le Age)) and (Age le 42))"); + } + softly.assertAll(); + } + + @Test + void testGuid() + { + final SimpleProperty.Guid id = new SimpleProperty.Guid<>(TestEntity.class, "Id"); + assertThat(id.equalTo(UUID.fromString("b3e130fe-d72c-4a5b-8dcf-463b497f985c")).getExpression(V4)) + .isEqualTo("(Id eq b3e130fe-d72c-4a5b-8dcf-463b497f985c)"); + } + + @Test + void testFilterableString() + { + final SoftAssertions softly = new SoftAssertions(); + final SimpleProperty.String name = new SimpleProperty.String<>(TestEntity.class, "Name"); + + softly.assertThat(name.equalTo((String) null).getExpression(V4)).isEqualTo("(Name eq null)"); + softly.assertThat(name.equalToNull().getExpression(V4)).isEqualTo("(Name eq null)"); + softly.assertThat(name.equalToNull().not().getExpression(V4)).isEqualTo("(not (Name eq null))"); + softly.assertThat(name.notEqualToNull().getExpression(V4)).isEqualTo("(Name ne null)"); + softly.assertThat(name.indexOf(name).getExpression(V4)).isEqualTo("indexof(Name,Name)"); + softly.assertThat(name.indexOf("Foo").getExpression(V4)).isEqualTo("indexof(Name,'Foo')"); + softly.assertThat(name.length().getExpression(V4)).isEqualTo("length(Name)"); + softly.assertThat(name.matches("Foo").getExpression(V4)).isEqualTo("matchesPattern(Name,'Foo')"); + softly.assertThat(name.toUpper().toLower().getExpression(V4)).isEqualTo("tolower(toupper(Name))"); + + softly + .assertThat(name.concat(name).concat("Foo").trim().getExpression(V4)) + .isEqualTo("trim(concat(concat(Name,Name),'Foo'))"); + + softly + .assertThat(name.substring(1).substring(2, 3).getExpression(V4)) + .isEqualTo("substring(substring(Name,1),2,3)"); + + softly + .assertThat(name.startsWith(name).and(name.startsWith("Foo")).getExpression(V4)) + .isEqualTo("(startswith(Name,Name) and startswith(Name,'Foo'))"); + + softly + .assertThat(name.endsWith(name).and(name.endsWith("Foo")).getExpression(V4)) + .isEqualTo("(endswith(Name,Name) and endswith(Name,'Foo'))"); + + softly + .assertThat(name.contains("Foo").and(name.contains(name)).getExpression(V4)) + .isEqualTo("(contains(Name,'Foo') and contains(Name,Name))"); + + softly.assertThat(name.equalTo("Qu'te").getExpression(V4)).isEqualTo("(Name eq 'Qu''te')"); + + softly.assertAll(); + } + + @Test + void testFilterableEnum() + { + final SoftAssertions softly = new SoftAssertions(); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.equalToNull().getExpression(V4)) + .isEqualTo("(ProgrammingLanguage eq null)"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.notEqualToNull().getExpression(V4)) + .isEqualTo("(ProgrammingLanguage ne null)"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.equalTo((TestEntity.Language) null).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage eq null)"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.notEqualTo((TestEntity.Language) null).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage ne null)"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.equalTo(TestEntity.Language.Java).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage eq SoftwareEngineer.Programming.Language'Java')"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.notEqualTo(TestEntity.Language.Java).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage ne SoftwareEngineer.Programming.Language'Java')"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.equalTo(TestEntity.PROGRAMMING_LANGUAGE).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage eq ProgrammingLanguage)"); + softly + .assertThat(TestEntity.PROGRAMMING_LANGUAGE.notEqualTo(TestEntity.PROGRAMMING_LANGUAGE).getExpression(V4)) + .isEqualTo("(ProgrammingLanguage ne ProgrammingLanguage)"); + softly.assertAll(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/BooleanQueriesTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/BooleanQueriesTest.java new file mode 100644 index 0000000000..35f2b2e9b4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/BooleanQueriesTest.java @@ -0,0 +1,46 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class BooleanQueriesTest +{ + @Test + void testGetFilteredWithNot() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.USER_NAME.length().greaterThanEqual(8).not()); + final String expectedUnencodedFilter = "(not (length(UserName) ge 8))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithAnd() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.FIRST_NAME.length().lessThanEqual(8).and(Person.LAST_NAME.length().lessThanEqual(8))); + final String expectedUnencodedFilter = "((length(FirstName) le 8) and (length(LastName) le 8))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithOr() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.FIRST_NAME.length().lessThanEqual(8).or(Person.LAST_NAME.length().lessThanEqual(8))); + final String expectedUnencodedFilter = "((length(FirstName) le 8) or (length(LastName) le 8))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CollectionQueriesTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CollectionQueriesTest.java new file mode 100644 index 0000000000..ff4c7f4216 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CollectionQueriesTest.java @@ -0,0 +1,51 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.PersonGender; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class CollectionQueriesTest +{ + @Test + void testGetFilteredWithAny() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.TO_FRIENDS.any(Person.FIRST_NAME.equalTo("Angel"))); + final String expectedUnencodedFilter = "Friends/any(a:(a/FirstName eq 'Angel'))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithAll() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.TO_FRIENDS.all(Person.FIRST_NAME.equalTo("Angel"))); + final String expectedUnencodedFilter = "Friends/all(a:(a/FirstName eq 'Angel'))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testEnumFilter() + { + + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.TO_FRIENDS.all(Person.GENDER.notEqualTo(PersonGender.MALE))); + + final String expectedUnencodedFilter = "Friends/all(a:(a/Gender ne Trippin.PersonGender'Male'))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ComparisonQueriesTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ComparisonQueriesTest.java new file mode 100644 index 0000000000..cdaa55102d --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ComparisonQueriesTest.java @@ -0,0 +1,112 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class ComparisonQueriesTest +{ + @Test + void testGetFilteredWithEquals() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().equalTo(5)); + final String expectedUnencodedFilter = "(length(Trips) eq 5)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithNotEquals() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().notEqualTo(10)); + final String expectedUnencodedFilter = "(length(Trips) ne 10)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithLessThan() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().lessThan(2)); + final String expectedUnencodedFilter = "(length(Trips) lt 2)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithGreaterThan() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().greaterThan(15)); + final String expectedUnencodedFilter = "(length(Trips) gt 15)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithLessThanOrEquals() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().lessThanEqual(1)); + final String expectedUnencodedFilter = "(length(Trips) le 1)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithGreaterThanOrEquals() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_TRIPS.length().greaterThanEqual(20)); + final String expectedUnencodedFilter = "(length(Trips) ge 20)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithInLiterals() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.USER_NAME.in("scottketchum", "javieralfred")); + final String expectedUnencodedFilter = "(UserName in ('scottketchum','javieralfred'))"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithInSimpleCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.USER_NAME.in(Person.EMAILS)); + final String expectedUnencodedFilter = "(UserName in Emails)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithInNavigationPropertyCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.TO_BEST_FRIEND.in(Person.TO_FRIENDS)); + final String expectedUnencodedFilter = "(BestFriend in Friends)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } + + @Test + void testGetFilteredWithInEnumCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().filter(Person.FAVORITE_FEATURE.in(Person.FEATURES)); + final String expectedUnencodedFilter = "(FavoriteFeature in Features)"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$filter", expectedUnencodedFilter); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CountEntityUnitTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CountEntityUnitTest.java new file mode 100644 index 0000000000..a502c39147 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/CountEntityUnitTest.java @@ -0,0 +1,110 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.ContentType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.google.gson.JsonSyntaxException; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataDeserializationException; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; + +@WireMockTest +class CountEntityUnitTest +{ + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + + private DefaultHttpDestination destination; + private TrippinService service; + + private static final String COUNT_REQUEST_URL = + String.format("%s/%s/$count", TrippinService.DEFAULT_SERVICE_PATH, "People"); + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + service = new DefaultTrippinService().withServicePath(TrippinService.DEFAULT_SERVICE_PATH); + } + + @Test + void testSuccessfulCount() + { + stubFor( + get(urlPathEqualTo(COUNT_REQUEST_URL)) + .withQueryParam("$filter", equalTo("contains(FirstName,'Bar')")) + .withQueryParam("$search", equalTo("\"Foo\"")) + .willReturn(okForContentType(ContentType.TEXT_PLAIN.getMimeType(), "42"))); + + final Long countResponse = + service.countPeople().search("Foo").filter(Person.FIRST_NAME.contains("Bar")).execute(destination); + + verify(1, getRequestedFor(urlPathEqualTo(COUNT_REQUEST_URL))); + + assertThat(countResponse).isEqualTo(42L); + } + + @Test + void testFailCountNull() + { + stubFor( + get(urlPathEqualTo(COUNT_REQUEST_URL)) + .willReturn(okForContentType(ContentType.TEXT_PLAIN.getMimeType(), "null"))); + + assertThatCode(() -> service.countPeople().execute(destination)) + .isInstanceOf(ODataDeserializationException.class) + .matches(e -> e.getCause().getMessage().contains("null")); + } + + @Test + void testFailCountEmpty() + { + stubFor( + get(urlPathEqualTo(COUNT_REQUEST_URL)) + .willReturn(okForContentType(ContentType.TEXT_PLAIN.getMimeType(), ""))); + + assertThatCode(() -> service.countPeople().execute(destination)) + .isInstanceOf(ODataDeserializationException.class) + .matches(e -> e.getCause().getMessage().contains("null")); + } + + @Test + void testFailCountJson() + { + stubFor(get(urlPathEqualTo(COUNT_REQUEST_URL)).willReturn(okJson("{\"foo\":\"bar\"}"))); + assertThatCode(() -> service.countPeople().execute(destination)) + .isInstanceOf(ODataDeserializationException.class); + } + + @Test + void testFailCountNoInteger() + { + stubFor( + get(urlPathEqualTo(COUNT_REQUEST_URL)) + .willReturn(okForContentType(ContentType.TEXT_PLAIN.getMimeType(), "a"))); + + assertThatCode(() -> service.countPeople().execute(destination)) + .isInstanceOf(ODataDeserializationException.class) + .hasCauseInstanceOf(JsonSyntaxException.class) + .hasRootCauseExactlyInstanceOf(NumberFormatException.class); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ETagTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ETagTest.java new file mode 100644 index 0000000000..1b1f60844e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ETagTest.java @@ -0,0 +1,375 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.TestUtility; +import com.sap.cloud.sdk.datamodel.odatav4.core.ModificationResponse; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; + +/** + * Tests the proper submission of the entity version identifier, transmitted as ETag in the HTTP headers. + */ +@WireMockTest +class ETagTest +{ + private static final String SERVICE_URL = "/some/service"; + private static final String ENTITY_COLLECTION = "People"; + private static final String GET_ALL_REQUEST_URL = SERVICE_URL + "/" + ENTITY_COLLECTION; + private static final String CREATE_URL = SERVICE_URL + "/" + ENTITY_COLLECTION; + private static final String GET_BY_KEY_REQUEST_URL = SERVICE_URL + "/" + ENTITY_COLLECTION + "('russellwhyte')"; + + private static final String GET_ALL_RESPONSE_BODY = readResourceFile("GetAllResponseBody.json"); + private static final String GET_BY_KEY_RESPONSE_BODY = readResourceFile("GetSingleResponseBody.json"); + + private static final String ETAG_RUSSELL = "W/\"123\""; + // the jetty under wiremock appends a '--gzip' to the ETag, therefore we need to check this adjusted ETag + private static final String ETAG_RUSSELL_GZIPPED = "W/\"123--gzip\""; + private static final String ETAG_RUSSELL_UPDATED = "W/\"456\""; + private static final String ETAG_SCOTT = "W/\"999\""; + + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_FETCH_VALUE = "fetch"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + + private DefaultHttpDestination destination; + private final TrippinService service = new DefaultTrippinService().withServicePath(SERVICE_URL); + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + + tellWiremockToReturnCsrfToken(); + } + + private void tellWiremockToReturnCsrfToken() + { + stubFor( + head(urlEqualTo(SERVICE_URL)) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_FETCH_VALUE)) + .willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + } + + private static String readResourceFile( final String resourceFileName ) + { + return TestUtility.readResourceFile(ETagTest.class, resourceFileName); + } + + @Test + void testParseETagFromGetAllResponse() + { + stubFor(get(urlEqualTo(GET_ALL_REQUEST_URL)).willReturn(okJson(GET_ALL_RESPONSE_BODY))); + + final List result = service.getAllPeople().execute(destination); + assertThat(result.get(0).getVersionIdentifier().get()).isEqualTo(ETAG_RUSSELL); + assertThat(result.get(1).getVersionIdentifier().get()).isEqualTo(ETAG_SCOTT); + } + + @Test + void testParseETagFromCreateResponse() + { + stubFor( + post(urlEqualTo(CREATE_URL)).willReturn(okJson(GET_BY_KEY_RESPONSE_BODY).withHeader("ETag", ETAG_RUSSELL))); + final Person person = Person.builder().firstName("Russel").build(); + final ModificationResponse result = service.createPeople(person).execute(destination); + + assertThat( + result + .getModifiedEntity() + .getVersionIdentifier() + .getOrElseThrow( + () -> new AssertionError("Expected version identifier to be present on Create response."))) + .isEqualTo(ETAG_RUSSELL_GZIPPED); + } + + @Test + void testParseETagFromUpdateResponse() + { + stubFor( + patch(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .willReturn(okJson(GET_BY_KEY_RESPONSE_BODY).withHeader("ETag", ETAG_RUSSELL))); + final Person person = Person.builder().userName("russellwhyte").build(); + final ModificationResponse result = service.updatePeople(person).execute(destination); + + assertThat( + result + .getModifiedEntity() + .getVersionIdentifier() + .getOrElseThrow( + () -> new AssertionError("Expected version identifier to be present on Update response."))) + .isEqualTo(ETAG_RUSSELL_GZIPPED); + } + + @Test + void testParseETagFromGetByKeyResponse() + { + stubFor( + get(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .willReturn(okJson(GET_BY_KEY_RESPONSE_BODY).withHeader("ETag", ETAG_RUSSELL))); + + final Person russel = service.getPeopleByKey("russellwhyte").execute(destination); + + assertThat( + russel + .getVersionIdentifier() + .getOrElseThrow( + () -> new AssertionError("Expected version identifier to be present on GetByKey response."))) + .isEqualTo(ETAG_RUSSELL_GZIPPED); + } + + @Test + void testUpdateWithPATCHContainsEtagByDefault() + { + stubFor( + patch(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL)) + .willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = service.updatePeople(russel).modifyingEntity().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify( + patchRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL))); + } + + @Test + void testUpdateWithPATCHLacksETagIfDisabled() + { + stubFor( + patch(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = + service.updatePeople(russel).modifyingEntity().disableVersionIdentifier().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify(patchRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withoutHeader(HttpHeaders.IF_MATCH)); + } + + @Test + void testUpdateWithPATCHMatchesAnyETagIfChosen() + { + stubFor( + patch(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = + service.updatePeople(russel).modifyingEntity().matchAnyVersionIdentifier().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify(patchRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withHeader(HttpHeaders.IF_MATCH, equalTo("*"))); + } + + @Test + void testUpdateWithPUTContainsETagByDefault() + { + stubFor( + put(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL)) + .willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = service.updatePeople(russel).replacingEntity().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify( + putRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL))); + } + + @Test + void testUpdateWithPUTLacksETagIfDisabled() + { + stubFor( + put(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = + service.updatePeople(russel).replacingEntity().disableVersionIdentifier().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify(putRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withoutHeader(HttpHeaders.IF_MATCH)); + } + + @Test + void testUpdateWithPUTLacksMatchesAnyETagIfChosen() + { + stubFor( + put(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent().withHeader("ETag", ETAG_RUSSELL_UPDATED))); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + final ModificationResponse result = + service.updatePeople(russel).replacingEntity().matchAnyVersionIdentifier().execute(destination); + + assertThat(russel.getVersionIdentifier()).containsExactly(ETAG_RUSSELL); // origin entity remains unchanged + assertThat(result.getUpdatedVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + assertThat(result.getModifiedEntity().getVersionIdentifier()).containsExactly(ETAG_RUSSELL_UPDATED); + + verify(putRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withHeader(HttpHeaders.IF_MATCH, equalTo("*"))); + } + + @Test + void testDeleteContainsETagByDefault() + { + stubFor( + delete(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL)) + .willReturn(noContent())); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + service.deletePeople(russel).execute(destination); + + verify( + deleteRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL))); + } + + @Test + void testDeleteLacksEtagIfDisabled() + { + stubFor(delete(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent())); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + service.deletePeople(russel).disableVersionIdentifier().execute(destination); + + verify(deleteRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withoutHeader(HttpHeaders.IF_MATCH)); + } + + @Test + void testDeleteMatchesAnyETagIfChosen() + { + stubFor(delete(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent())); + + final Person russel = new Person(); + russel.setUserName("russellwhyte"); + russel.setVersionIdentifier(ETAG_RUSSELL); + + service.deletePeople(russel).matchAnyVersionIdentifier().execute(destination); + + verify(deleteRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withHeader(HttpHeaders.IF_MATCH, equalTo("*"))); + } + + @Test + void testNoETagReceived() + { + stubFor(get(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(okJson(GET_BY_KEY_RESPONSE_BODY))); + final Person person = service.getPeopleByKey("russellwhyte").execute(destination); + + assertThat(person.getVersionIdentifier()).isEmpty(); + } + + @Test + void testNoIfMatchIsSentOnEmptyETag() + { + stubFor(any(urlEqualTo(GET_BY_KEY_REQUEST_URL)).willReturn(noContent())); + + final Person person = new Person(); + person.setUserName("russellwhyte"); + + service.updatePeople(person).execute(destination); + service.deletePeople(person).execute(destination); + + verify(2, anyRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)).withoutHeader(HttpHeaders.IF_MATCH)); + } + + @Test + void testOverwritingExistingETagOnEntity() + { + stubFor( + any(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL_UPDATED)) + .willReturn(noContent())); + + final Person person = new Person(); + person.setUserName("russellwhyte"); + person.setVersionIdentifier(ETAG_RUSSELL); + + service.deletePeople(person).withHeader(HttpHeaders.IF_MATCH, ETAG_RUSSELL_UPDATED).execute(destination); + service + .updatePeople(person) + .modifyingEntity() + .withHeader(HttpHeaders.IF_MATCH, ETAG_RUSSELL_UPDATED) + .execute(destination); + + verify( + 2, + anyRequestedFor(urlEqualTo(GET_BY_KEY_REQUEST_URL)) + .withHeader(HttpHeaders.IF_MATCH, equalTo(ETAG_RUSSELL_UPDATED))); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedParameterTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedParameterTest.java new file mode 100644 index 0000000000..dad74c4170 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedParameterTest.java @@ -0,0 +1,21 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class EncodedParameterTest +{ + @Test + void testGetByKeyWithSpecialCharacters() + { + final String fullQueryWithSpecialCharacters = + DefaultTrippinService.DEFAULT_SERVICE_PATH + "/People('" + "test%2F%3F%20%23&user''%25$" + "')"; + final GetByKeyRequestBuilder request = new DefaultTrippinService().getPeopleByKey("test/? #&user'%$"); + assertThat(request.toRequest().getRelativeUri()).hasToString(fullQueryWithSpecialCharacters); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedQueryTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedQueryTest.java new file mode 100644 index 0000000000..bba02668a9 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/EncodedQueryTest.java @@ -0,0 +1,139 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class EncodedQueryTest +{ + @Test + void testEncodingInFilter() + { + final ODataRequestRead query = + new DefaultTrippinService() + .getAllPeople() + .filter( + Person.USER_NAME + .length() + .greaterThanEqual(8) + .not() + .and(Person.FIRST_NAME.length().greaterThanEqual(10)) + .or(Person.LAST_NAME.length().lessThanEqual(7))) + .toRequest(); + + final String expectedEncodedQuery = + "$filter=(((not%20(length(UserName)%20ge%208))%20and%20(length(FirstName)%20ge%2010))%20or%20(length(LastName)%20le%207))"; + + assertThat(query.getRequestQuery()).isEqualTo(expectedEncodedQuery); + } + + @Test + void testSafeCharsInFilter() + { + final ODataRequestRead query = + new DefaultTrippinService().getAllPeople().filter(Person.USER_NAME.contains("_*-:,/'().")).toRequest(); + + final String expectedEncodedQuery = "$filter=contains(UserName,'_*-:,/''().')"; + + assertThat(query.getRequestQuery()).isEqualTo(expectedEncodedQuery); + } + + @Test + void testSpecialCharsInFilter() + { + final ODataRequestRead query = + new DefaultTrippinService().getAllPeople().filter(Person.USER_NAME.contains("!@#$%^&=+|\\\"")).toRequest(); + + final String expectedEncodedQuery = "$filter=contains(UserName,'%21%40%23%24%25%5E%26%3D%2B%7C%5C%22')"; + + assertThat(query.getRequestQuery()).isEqualTo(expectedEncodedQuery); + } + + @Test + void testEncodingForeignCharactersInFilter() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter( + Person.USER_NAME + .equalTo("François") + .and(Person.LAST_NAME.contains("Sørina").or(Person.FIRST_NAME.matches("维基百科")))); + + final String expectedEncodedQuery = + "$filter=((UserName%20eq%20'Fran%C3%A7ois')%20and%20(contains(LastName,'S%C3%B8rina')%20or%20matchesPattern(FirstName,'%E7%BB%B4%E5%9F%BA%E7%99%BE%E7%A7%91')))"; + + assertThat(query.toRequest().getRequestQuery()).isEqualTo(expectedEncodedQuery); + } + + @Test + void testEncodingInNestedFiltersWithAllSpecialCharacters() + { + final ODataRequestRead request = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.FIRST_NAME.contains("% $&#?\"\\+'")) + .select(Person.TO_BEST_FRIEND.select(Person.TO_TRIPS.filter(Trip.NAME.contains("% $&#?\"\\+'")))) + .toRequest(); + final String expected = + "$expand=BestFriend($expand=Trips($filter=contains(Name,'%25%20%24%26%23%3F%22%5C%2B''')))&$filter=contains(FirstName,'%25%20%24%26%23%3F%22%5C%2B''')"; + + assertThat(request.getRequestQuery()).isEqualTo(expected); + } + + @Test + void testEncodingInSearchQuery() + { + final ODataRequestRead search = + new DefaultTrippinService().getAllPeople().search("Hash # Quoted \"string\" Escaped \\").toRequest(); + final String query = search.getRequestQuery(); + + final String expected = "$search=%22Hash%20%23%20Quoted%20%5C%22string%5C%22%20Escaped%20%5C%5C%22"; + + assertThat(query).isEqualTo(expected); + } + + @Test + void testEncodingInOrderBy() + { + final ODataRequestRead request = + new DefaultTrippinService() + .getAllPeople() + .orderBy(Person.FIRST_NAME.asc(), Person.LAST_NAME.desc()) + .toRequest(); + + final String expected = "$orderby=FirstName%20asc,LastName%20desc"; + + assertThat(request.getRequestQuery()).isEqualTo(expected); + } + + @Test + void testEncodingInCustomQueryParametersWithAllSpecialCharacters() + { + final ODataRequestRead request = + new DefaultTrippinService() + .getAllPeople() + .withQueryParameter("foo", "hash#tag") + .withQueryParameter("param", "% $&#?\"\\+'bar") + .toRequest(); + final String expected = "foo=hash%23tag¶m=%25%20%24%26%23%3F%22%5C%2B'bar"; + + assertThat(request.getRequestQuery()).isEqualTo(expected); + } + + @Test + void testSafeCharsInCustomQueryParameters() + { + final ODataRequestRead request = + new DefaultTrippinService().getAllPeople().withQueryParameter("foo", "_*-:,/'().").toRequest(); + final String expected = "foo=_*-:,/'()."; + + assertThat(request.getRequestQuery()).isEqualTo(expected); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchReferenceServiceIntegrationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchReferenceServiceIntegrationTest.java new file mode 100644 index 0000000000..4b590ba9e0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchReferenceServiceIntegrationTest.java @@ -0,0 +1,130 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.google.gson.Gson; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataRequestException; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +@Disabled( "Test runs against a v4 reference service on odata.org. Use it only to manually verify behaviour." ) +class ODataClientBatchReferenceServiceIntegrationTest +{ + private Destination httpDestination; + + @BeforeEach + void configure() + throws IOException, + ParseException + { + httpDestination = TripPinUtility.getDestination(); + } + + @Test + void testEmptyBatch() + throws IOException, + ParseException + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4).execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // response HTTP code is healthy + final ClassicHttpResponse httpResponse = batchResponse.getHttpResponse(); + assertThat(httpResponse.getCode()).isEqualTo(200); + + // response payload can be extracted + final String response = EntityUtils.toString(batchResponse.getHttpResponse().getEntity()); + assertThat(response).matches("^--batchresponse_[a-f0-9-]+--\r\n$"); + } + + @Test + void testBatchWithSingleRead() + throws IOException, + ParseException + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4) + .addRead(new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4)) + .execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // response HTTP code is healthy + final ClassicHttpResponse httpResponse = batchResponse.getHttpResponse(); + assertThat(httpResponse.getCode()).isEqualTo(200); + + // response payload can be extracted + final String response = EntityUtils.toString(batchResponse.getHttpResponse().getEntity()); + assertThat(response).isNotEmpty(); + + // response payload contains expected JSON result + final Matcher matcher = Pattern.compile("\"value\":\\[(.*?)]}\r\n").matcher(response); + assertThat(matcher.find()).isTrue(); + + // response JSON contains a valid Person + final Person person = new Gson().fromJson(matcher.group(1), Person.class); + assertThat(person).isNotNull().matches(p -> p.getUserName() != null); + } + + @Test + void testBatchWithReadWrite() + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4) + .addRead(new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4)) + .beginChangeset() + .addCreate(new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe1\"}", ODataProtocol.V4)) + .endChangeset() + .execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // response HTTP code is healthy + final ClassicHttpResponse httpResponse = batchResponse.getHttpResponse(); + assertThat(httpResponse.getCode()).isEqualTo(200); + } + + @Test + void testBatchErrorWithDifferentServicePath() + { + final HttpClient httpClient = mock(HttpClient.class); + + assertThatCode( + () -> new ODataRequestBatch("this/", ODataProtocol.V4) + .addRead(new ODataRequestRead("this/", "People", "$top=1", ODataProtocol.V4)) + .addRead(new ODataRequestRead("other/", "People", "$top=2", ODataProtocol.V4)) + .execute(httpClient)) + .isInstanceOf(ODataRequestException.class); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingIntegrationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingIntegrationTest.java new file mode 100644 index 0000000000..0caf4ca4da --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingIntegrationTest.java @@ -0,0 +1,142 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.io.IOException; +import java.util.List; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; +import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +@Disabled( "Test runs against a v4 reference service on odata.org. Use it only to manually verify behaviour." ) +class ODataClientBatchResponseParsingIntegrationTest +{ + private Destination httpDestination; + + @BeforeEach + void configure() + throws IOException + { + httpDestination = TripPinUtility.getDestination(); + } + + @Test + void testEmptyBatch() + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4).execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // extract unrequested read result + final ODataRequestRead read = new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4); + assertThatCode(() -> batchResponse.getResult(read)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testBatchWithReads() + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestRead read1 = new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4); + final ODataRequestRead read2 = new ODataRequestRead("/", "People", "$top=2&$skip=1", ODataProtocol.V4); + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4).addRead(read1).addRead(read2).execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // response HTTP code is healthy + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // response payload can be extracted + final List people1 = batchResponse.getResult(read1).asList(Person.class); + assertThat(people1).hasSize(1).doesNotContainNull(); + + final List people2 = batchResponse.getResult(read2).asList(Person.class); + assertThat(people2).hasSize(2).doesNotContainNull().doesNotContainAnyElementsOf(people1); + } + + @Test + void testBatchWithReadsAndWrites() + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestRead read1 = new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4); + final ODataRequestRead read2 = new ODataRequestRead("/", "People", "$top=2&$skip=1", ODataProtocol.V4); + + final ODataRequestCreate create1 = + new ODataRequestCreate( + "/", + "People", + "{\"UserName\":\"JohnDoe1\", \"FirstName\":\"John\"}", + ODataProtocol.V4); + final ODataRequestCreate create2 = + new ODataRequestCreate( + "/", + "People", + "{\"UserName\":\"JohnDoe2\", \"FirstName\":\"John\"}", + ODataProtocol.V4); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4) + .addRead(read1) + .beginChangeset() + .addCreate(create1) + .addCreate(create2) + .endChangeset() + .addRead(read2) + .execute(httpClient); + + // response object not null + assertThat(batchResponse).isNotNull(); + + // response parsing + final List resultRead1 = batchResponse.getResult(read1).asList(Person.class); + final List resultRead2 = batchResponse.getResult(read2).asList(Person.class); + assertThat(resultRead1).isNotNull().hasSize(1).doesNotContainNull().doesNotContainAnyElementsOf(resultRead2); + assertThat(resultRead2).isNotNull().hasSize(2).doesNotContainNull().doesNotContainAnyElementsOf(resultRead1); + + assertThat(batchResponse.getResult(create1).as(Person.class)).isNotNull(); + assertThat(batchResponse.getResult(create2).as(Person.class)).isNotNull(); + + // response HTTP code is healthy + final ClassicHttpResponse httpResponse = batchResponse.getHttpResponse(); + assertThat(httpResponse.getCode()).isEqualTo(200); + } + + @Test + void testBatchResultWithError() + throws IOException + { + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(httpDestination); + + final ODataRequestRead read = new ODataRequestRead("/", "People", "$top=1", ODataProtocol.V4); + + final ODataRequestResultMultipartGeneric batchResponse = + new ODataRequestBatch("/", ODataProtocol.V4).addRead(read).execute(httpClient); + + // consume result + EntityUtils.consume(batchResponse.getHttpResponse().getEntity()); + + // extract unrequested read result + assertThatCode(() -> batchResponse.getResult(read)).isInstanceOf(IllegalStateException.class); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingUnitTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingUnitTest.java new file mode 100644 index 0000000000..28ce581daa --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataClientBatchResponseParsingUnitTest.java @@ -0,0 +1,399 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V4; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.junit.jupiter.api.Test; + +import com.google.common.base.Objects; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataResponseException; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestBatch; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestCreate; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestRead; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultGeneric; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestResultMultipartGeneric; +import com.sap.cloud.sdk.datamodel.odatav4.TestUtility; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +import io.vavr.control.Try; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class ODataClientBatchResponseParsingUnitTest +{ + private final AtomicInteger uuidCounter = new AtomicInteger(0); + private final Supplier uuidProvider = () -> new UUID(0, uuidCounter.incrementAndGet()); + + @Test + void testEmptyBatch() + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchEmptyRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchEmptyResponse.txt"); + + // Prepare test objects + final ODataRequestBatch batchRequest = new ODataRequestBatch("/", V4, uuidProvider); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // extract unrequested read result + final ODataRequestRead read = new ODataRequestRead("/", "People", "$top=1", V4); + assertThatCode(() -> batchResponse.getResult(read)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testBatchWithOnlyReads() + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchOnlyReadsRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchOnlyReadsResponse.txt"); + + // Prepare test objects + final ODataRequestRead read1 = new ODataRequestRead("/", "People", "$top=1", V4); + final ODataRequestRead read2 = new ODataRequestRead("/", "People", "$top=2&$skip=1", V4); + final ODataRequestBatch batchRequest = + new ODataRequestBatch("/", V4, uuidProvider).addRead(read1).addRead(read2); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: response payload can be extracted + final List resultRead1 = batchResponse.getResult(read1).asList(Person.class); + final List resultRead2 = batchResponse.getResult(read2).asList(Person.class); + assertThat(resultRead1).isNotNull().hasSize(1).doesNotContainNull().doesNotContainAnyElementsOf(resultRead2); + assertThat(resultRead2).isNotNull().hasSize(2).doesNotContainNull().doesNotContainAnyElementsOf(resultRead1); + } + + @Test + void testBatchWithReadsOnMissingResponse() + { + // 2 requests but only 1 response + final String requestBody = readResourceFileCrlf("BatchOnlyReadsMissingRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchOnlyReadsMissingResponse.txt"); + + // Prepare test objects + final ODataEntityKey entityKey1 = new ODataEntityKey(V4).addKeyProperty("key", "one"); + final ODataRequestReadByKey readByKey1 = new ODataRequestReadByKey("/", "People", entityKey1, "", V4); + final ODataEntityKey entityKey2 = new ODataEntityKey(V4).addKeyProperty("key", "two"); + final ODataRequestReadByKey readByKey2 = new ODataRequestReadByKey("/", "People", entityKey2, "", V4); + final ODataEntityKey entityKey3 = new ODataEntityKey(V4).addKeyProperty("key", "three"); + final ODataRequestReadByKey readByKey3 = new ODataRequestReadByKey("/", "People", entityKey3, "", V4); + final ODataRequestBatch batchRequest = + new ODataRequestBatch("/", V4, uuidProvider) + .addReadByKey(readByKey1) + .addReadByKey(readByKey2) + .addReadByKey(readByKey3); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: + // response payload1 is 200 + assertThat(batchResponse.getResult(readByKey1)).isNotNull(); + + // response payload2 is 404 + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getResult(readByKey2)) + .satisfies(e -> assertThat(e.getHttpCode()).isEqualTo(404)); + + // response payload3 cannot be extracted, response is missing + assertThatExceptionOfType(ODataResponseException.class) + .isThrownBy(() -> batchResponse.getResult(readByKey3)) + .withMessage("Unable to extract batch response item at position 3. The response contains only 2 items."); + } + + @Test + void testBatchWithErrorReads() + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchOnlyReadsRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchOnlyReadsErrorResponse.txt"); + + // Prepare test objects + final ODataRequestRead read1 = new ODataRequestRead("/", "People", "$top=1", V4); + final ODataRequestRead read2 = new ODataRequestRead("/", "People", "$top=2&$skip=1", V4); + final ODataRequestBatch batchRequest = + new ODataRequestBatch("/", V4, uuidProvider).addRead(read1).addRead(read2); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: response payload can be extracted + assertThat(batchResponse.getResult(read1)).isNotNull(); + + // batchResponse.getResult(read2); // expected error: + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getResult(read2)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getOdataError().getODataCode()).isEqualTo("ZCU/100"); + }); + } + + @Test + void testBatchWithReadsAndWrites() + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchReadsAndWritesSuccessRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchReadsAndWritesSuccessResponse.txt"); + + // Prepare test objects + final ODataRequestCreate create1 = + new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe1\", \"FirstName\":\"John\"}", V4); + final ODataRequestCreate create2 = + new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe2\", \"FirstName\":\"John\"}", V4); + final ODataEntityKey entityKey = new ODataEntityKey(V4).addKeyProperty("key", "foo"); + final ODataRequestReadByKey readByKey = new ODataRequestReadByKey("/", "People", entityKey, "", V4); + final ODataRequestBatch batchRequest = + new ODataRequestBatch("/", V4, uuidProvider) + .beginChangeset() + .addCreate(create1) + .addCreate(create2) + .endChangeset() + .addReadByKey(readByKey); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: response parsing + final ODataRequestResultGeneric resultCreate1 = batchResponse.getResult(create1); + assertThat(resultCreate1).isNotNull(); + assertThat(resultCreate1.as(Person.class)).isNotNull().extracting(Person::getUserName).isEqualTo("JohnDoe1"); + assertThat(resultCreate1.getHttpResponse().getCode()).isEqualTo(201); + + final ODataRequestResultGeneric resultCreate2 = batchResponse.getResult(create2); + assertThat(resultCreate2).isNotNull(); + assertThat(resultCreate2.as(Person.class)).isNotNull().extracting(Person::getUserName).isEqualTo("JohnDoe2"); + assertThat(resultCreate2.getHttpResponse().getCode()).isEqualTo(201); + + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getResult(readByKey)) + .satisfies(e -> assertThat(e.getHttpCode()).isEqualTo(404)); + } + + @Test + void testBatchWithErrorInChangeset() + throws IOException + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchReadsAndWritesErrorRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchReadsAndWritesErrorResponse.txt"); + + // Prepare test objects + final ODataRequestCreate create1 = + new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe1\", \"FirstName\":\"John\"}", V4); + final ODataRequestCreate create2 = new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe2\"}", V4); + final ODataEntityKey entityKey = new ODataEntityKey(V4).addKeyProperty("key", "klauskinski"); + final ODataRequestReadByKey readByKey = new ODataRequestReadByKey("/", "People", entityKey, "", V4); + final ODataRequestBatch requestBatch = + new ODataRequestBatch("/", V4, uuidProvider) + .beginChangeset() + .addCreate(create1) + .addCreate(create2) + .endChangeset() + .addReadByKey(readByKey); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = requestBatch.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: response parsing + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getResult(create1)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getHttpBody().get()).contains("The FirstName field is required"); + assertThat(e.getRequest()).isSameAs(create2); + }); + + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getResult(create2)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getHttpBody().get()).contains("The FirstName field is required"); + assertThat(e.getRequest()).isSameAs(create2); + }); + + final ODataRequestResultGeneric resultReadByKey = batchResponse.getResult(readByKey); + assertThat(resultReadByKey).isNotNull(); + assertThat(resultReadByKey.getHttpResponse().getCode()).isEqualTo(200); + } + + @Test + void testBatchWithErrorInsteadOfChangeset() + throws IOException + { + // Read OData response json + final String requestBody = readResourceFileCrlf("BatchReadsAndWritesErrorRequest.txt"); + final String responseBody = readResourceFileCrlf("BatchReadsAndWritesErrorResponseWithoutChangeset.txt"); + + // Prepare test objects + final ODataRequestCreate create1 = + new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe1\", \"FirstName\":\"John\"}", V4); + final ODataRequestCreate create2 = new ODataRequestCreate("/", "People", "{\"UserName\":\"JohnDoe2\"}", V4); + final ODataEntityKey entityKey = new ODataEntityKey(V4).addKeyProperty("key", "klauskinski"); + final ODataRequestReadByKey readByKey = new ODataRequestReadByKey("/", "People", entityKey, "", V4); + final ODataRequestBatch batchRequest = + new ODataRequestBatch("/", V4, uuidProvider) + .beginChangeset() + .addCreate(create1) + .addCreate(create2) + .endChangeset() + .addReadByKey(readByKey); + + final HttpClient httpClient = MockedHttpClient.of(requestBody, responseBody); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + // Test assertion: response object not null and healthy + assertThat(batchResponse).isNotNull(); + assertThat(batchResponse.getHttpResponse().getCode()).isEqualTo(200); + + // Test assertion: response parsing + assertThatExceptionOfType(ODataResponseException.class) + .isThrownBy(() -> batchResponse.getResult(create1)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getHttpBody().get()).contains("The FirstName field is required"); + assertThat(e.getRequest()).isSameAs(create2); + }); + + assertThatExceptionOfType(ODataResponseException.class) + .isThrownBy(() -> batchResponse.getResult(create2)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getHttpBody().get()).contains("The FirstName field is required"); + assertThat(e.getRequest()).isSameAs(create2); + }); + + final ODataRequestResultGeneric resultReadByKey = batchResponse.getResult(readByKey); + assertThat(resultReadByKey).isNotNull(); + assertThat(resultReadByKey.getHttpResponse().getCode()).isEqualTo(200); + } + + @Test + void testBatchResultWithError() + { + // Prepare test objects + final ODataRequestRead read = new ODataRequestRead("/", "People", "$top=1", V4); + final ODataRequestBatch batchRequest = new ODataRequestBatch("/", V4, uuidProvider).addRead(read); + + final HttpClient httpClient = MockedHttpClient.of(null, null); + final ODataRequestResultMultipartGeneric batchResponse = batchRequest.execute(httpClient); + + assertThat(batchResponse.getHttpResponse().getEntity()).isNull(); + + // Extract requested read result - entity already consumed + assertThatCode(() -> batchResponse.getResult(read)).isInstanceOf(ODataResponseException.class); + } + + private static class MockedHttpClient + { + @Getter + private final HttpClient httpClient = mock(HttpClient.class); + + @SneakyThrows + static HttpClient of( @Nullable final String requestBody, @Nullable final String responseBody ) + { + final ClassicHttpResponse odataResponse = new BasicClassicHttpResponse(200, "OK"); + if( responseBody != null ) { + odataResponse.setEntity(new StringEntity(responseBody)); + final String batchDelimiter = responseBody.substring(2, responseBody.indexOf("\r")); + odataResponse.setHeader(HttpHeaders.CONTENT_TYPE, "multipart/mixed; boundary=" + batchDelimiter); + } + + final HttpClient httpClient = mock(HttpClient.class); + doReturn(odataResponse) + .when(httpClient) + .executeOpen(isNull(), argThat(req -> isCorrectODataRequest(req, requestBody)), isNull()); + + return httpClient; + } + + static private + boolean + isCorrectODataRequest( @Nonnull final ClassicHttpRequest request, @Nullable final String assertRequestBody ) + { + if( !(request instanceof HttpPost) ) { + log.error("Expected HTTP POST request."); + return false; + } + + final Header requestedContentType = request.getFirstHeader(HttpHeaders.CONTENT_TYPE); + final String expectedContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + if( !requestedContentType.getValue().equals(expectedContentType) ) { + log.error("Expected content type: {}", expectedContentType); + return false; + } + + if( assertRequestBody == null ) { + return true; + } + + final String requestCont = Try.of(() -> EntityUtils.toString(((HttpPost) request).getEntity())).getOrNull(); + if( !Objects.equal(requestCont, assertRequestBody) ) { + log.error("Expected request content: {}, but got {}", assertRequestBody, requestCont); + return false; + } + + return true; + } + } + + private static String readResourceFileCrlf( final String file ) + { + return TestUtility.readResourceFileCrlf(ODataClientBatchResponseParsingUnitTest.class, file); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceByKeyTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceByKeyTest.java new file mode 100644 index 0000000000..3233416dd1 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceByKeyTest.java @@ -0,0 +1,96 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataRequestReadByKey; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class ODataReferenceServiceByKeyTest +{ + private static final String testUserName = "testuser"; + private static final String keyWithSpecialCharacters = "test''user"; + private static final String fullQuery = + DefaultTrippinService.DEFAULT_SERVICE_PATH + "/People('" + testUserName + "')?%s"; + private static final String fullQueryWithSpecialCharacters = + DefaultTrippinService.DEFAULT_SERVICE_PATH + "/People('" + keyWithSpecialCharacters + "')"; + + @Test + void testSelectAndExpand() + { + final ODataRequestReadByKey request = + new DefaultTrippinService() + .getPeopleByKey(testUserName) + .select(Person.FIRST_NAME, Person.LAST_NAME) + .select(Person.TO_TRIPS) + .toRequest(); + + final String expected = "$select=FirstName,LastName&$expand=Trips"; + + assertThat(request.getQueryString()).isEqualTo(expected); + assertThat(request.getRelativeUri()).hasToString(String.format(fullQuery, expected)); + } + + @Test + void testMultipleExpandsWithFilter() + { + final ODataRequestReadByKey request = + new DefaultTrippinService() + .getPeopleByKey(testUserName) + .select(Person.TO_TRIPS.select(Trip.NAME)) + .select(Person.TO_BEST_FRIEND.select(Person.LAST_NAME)) + .toRequest(); + + final String expected = "$expand=Trips($select=Name),BestFriend($select=LastName)"; + + assertThat(request.getQueryString()).isEqualTo(expected); + assertThat(request.getRelativeUri()).hasToString(String.format(fullQuery, expected)); + } + + @Test + void testAllSelectionVariants() + { + final ODataRequestReadByKey request = + new DefaultTrippinService() + .getPeopleByKey(testUserName) + .select(Person.GENDER, Person.USER_NAME) + .select(Person.EMAILS) + .select(Person.TO_BEST_FRIEND, Person.TO_TRIPS, Person.TO_FRIENDS.top(2).skip(1)) + .toRequest(); + + final String expected = "$select=Gender,UserName,Emails&$expand=BestFriend,Trips,Friends($top=2;$skip=1)"; + + assertThat(request.getQueryString()).isEqualTo(expected); + assertThat(request.getRelativeUri()).hasToString(String.format(fullQuery, expected)); + } + + @Test + void testGetByKeyWithSpecialCharacters() + { + final ODataRequestReadByKey request = new DefaultTrippinService().getPeopleByKey("test'user").toRequest(); + assertThat(request.getRelativeUri()).hasToString(fullQueryWithSpecialCharacters); + } + + @Test + void testAllDuplicateSelections() + { + final ODataRequestReadByKey request = + new DefaultTrippinService() + .getPeopleByKey(testUserName) + .select(Person.GENDER, Person.USER_NAME) + .select(Person.GENDER, Person.USER_NAME) + .select(Person.TO_BEST_FRIEND, Person.TO_TRIPS) + .select(Person.TO_BEST_FRIEND, Person.TO_BEST_FRIEND.select(Person.TO_FRIENDS)) + .select(Person.TO_FRIENDS, Person.TO_FRIENDS.top(2).skip(1)) + .toRequest(); + + final String expected = + "$select=Gender,UserName&$expand=Trips,BestFriend($expand=Friends),Friends($top=2;$skip=1)"; + + assertThat(request.getQueryString()).isEqualTo(expected); + assertThat(request.getRelativeUri()).hasToString(String.format(fullQuery, expected)); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceTest.java new file mode 100644 index 0000000000..480aef01cb --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataReferenceServiceTest.java @@ -0,0 +1,132 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class ODataReferenceServiceTest +{ + @Test + void testSelectAndExpand() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .select(Person.FIRST_NAME, Person.LAST_NAME) + .select(Person.TO_TRIPS); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$select", "FirstName,LastName") + .hasParameter("$expand", "Trips"); + } + + @Test + void testMultipleExpandsWithFilter() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .select(Person.TO_TRIPS.select(Trip.NAME)) + .select(Person.TO_BEST_FRIEND.select(Person.LAST_NAME)); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$expand", "Trips($select=Name),BestFriend($select=LastName)"); + } + + @Test + void testAllSelectionVariants() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .select(Person.GENDER, Person.USER_NAME) + .select(Person.TO_BEST_FRIEND, Person.TO_TRIPS, Person.TO_FRIENDS.top(2).skip(1)) + .filter(Person.EMAILS.contains(Collections.singletonList("ASD"))); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$select", "Gender,UserName") + .hasParameter("$expand", "BestFriend,Trips,Friends($top=2;$skip=1)") + .hasParameter("$filter", "contains(Emails,['ASD'])"); + } + + @Test + void testFiltersWithSpecialCharacters() + { + final GetAllRequestBuilder request = + new DefaultTrippinService().getAllPeople().filter(Person.FIRST_NAME.contains("' +&#\\")); + + assertThat(request.toRequest().getRelativeUri()).hasParameter("$filter", "contains(FirstName,''' +&#\\')"); + } + + @Test + void testNestedFiltersWithAllSpecialCharacters() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.FIRST_NAME.contains("% $&#?\"\\+'")) + .select(Person.TO_BEST_FRIEND.select(Person.TO_TRIPS.filter(Trip.NAME.contains("% $&#?\"\\+'")))); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$expand", "BestFriend($expand=Trips($filter=contains(Name,'% $&#?\"\\+''')))") + .hasParameter("$filter", "contains(FirstName,'% $&#?\"\\+''')"); + } + + @Test + void testNestedFiltersWithSpecialCharacters() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.FIRST_NAME.contains("' +&#\\")) + .select(Person.TO_TRIPS.filter(Trip.NAME.equalTo("Trip in '&USA'#"))); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$expand", "Trips($filter=(Name eq 'Trip in ''&USA''#'))") + .hasParameter("$filter", "contains(FirstName,''' +&#\\')"); + } + + @Test + void testAllDuplicateSelections() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .select(Person.GENDER, Person.USER_NAME) + .select(Person.GENDER, Person.USER_NAME) + .select(Person.TO_BEST_FRIEND, Person.TO_TRIPS) + .select(Person.TO_BEST_FRIEND, Person.TO_BEST_FRIEND.select(Person.TO_FRIENDS)) + .select(Person.TO_FRIENDS, Person.TO_FRIENDS.top(2).skip(1)); + + assertThat(request.toRequest().getRelativeUri()) + .hasParameter("$select", "Gender,UserName") + .hasParameter("$expand", "Trips,BestFriend($expand=Friends),Friends($top=2;$skip=1)"); + } + + @Test + void testOrderBy() + { + final GetAllRequestBuilder request = + new DefaultTrippinService().getAllPeople().orderBy(Person.FIRST_NAME.asc(), Person.LAST_NAME.desc()); + + assertThat(request.toRequest().getRelativeUri()).hasParameter("$orderby", "FirstName asc,LastName desc"); + } + + @Test + void testBadOrderByUsage() + { + final GetAllRequestBuilder request = + new DefaultTrippinService() + .getAllPeople() + .orderBy(Person.FIRST_NAME.desc(), Person.LAST_NAME.asc(), Person.FIRST_NAME.desc()); + + assertThat(request.toRequest().getRelativeUri()).hasParameter("$orderby", "FirstName desc,LastName asc"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataV4BatchReferenceServiceUnitTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataV4BatchReferenceServiceUnitTest.java new file mode 100644 index 0000000000..1e501349b2 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/ODataV4BatchReferenceServiceUnitTest.java @@ -0,0 +1,241 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.sap.cloud.sdk.datamodel.odatav4.TestUtility.readResourceFileCrlf; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odata.client.exception.ODataServiceErrorException; +import com.sap.cloud.sdk.datamodel.odatav4.core.BatchRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.BatchResponse; +import com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; + +@WireMockTest +class ODataV4BatchReferenceServiceUnitTest +{ + private static final WireMockConfiguration WIREMOCK_CONFIGURATION = wireMockConfig().dynamicPort(); + + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + private static final String X_CSRF_TOKEN_HEADER_FETCH_VALUE = "fetch"; + private static final String X_CSRF_TOKEN_HEADER_VALUE = "awesome-csrf-token"; + + private DefaultHttpDestination destination; + private TrippinService service; + private BatchRequestBuilder sut; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + service = new DefaultTrippinService(); + + final AtomicInteger uuidCounter = new AtomicInteger(); + + sut = new BatchRequestBuilder(TrippinService.DEFAULT_SERVICE_PATH) + { + @Override + protected Supplier getUuidProvider() + { + return () -> new UUID(0, uuidCounter.incrementAndGet()); + } + }; + + // mock CSRF token retrieval + stubFor(head(anyUrl()).willReturn(ok())); + stubFor( + head(urlEqualTo("/")) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, equalTo(X_CSRF_TOKEN_HEADER_FETCH_VALUE)) + .willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, X_CSRF_TOKEN_HEADER_VALUE))); + } + + @Test + void testEmptyBatch() + { + // Read OData response json + final String requestBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchEmptyRequest.txt"); + final String responseBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchEmptyResponse.txt"); + final String batchRequestUrl = String.format("%s%s", TrippinService.DEFAULT_SERVICE_PATH, "/$batch"); + + // Mocking OData response + final String responseContentType = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + stubFor( + post(urlEqualTo(batchRequestUrl)) + .willReturn(okForContentType(responseContentType, responseBody).withStatus(HttpStatus.SC_ACCEPTED))); + + // Run payload + final BatchResponse batchResponse = sut.execute(destination); + + // Verify request body + final String requestContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + verify( + postRequestedFor(urlEqualTo(batchRequestUrl)) + .withoutHeader(HttpHeaders.ACCEPT) + .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(requestContentType)) + .withRequestBody(equalTo(requestBody))); + + // Test assertion: response object not null and healthy + assertThat(batchResponse.getResponseStatusCode()).isEqualTo(HttpStatus.SC_ACCEPTED); + assertThat(batchResponse.getResponseHeaders()).isNotEmpty(); + } + + @Test + void testBatchWithOnlyReads() + { + // Read OData response json + final String requestBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchOnlyReadsRequest.txt"); + final String responseBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchOnlyReadsResponse.txt"); + final String batchRequestUrl = String.format("%s%s", TrippinService.DEFAULT_SERVICE_PATH, "/$batch"); + + // Mocking OData response + final String responseContentType = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + stubFor( + post(urlEqualTo(batchRequestUrl)) + .willReturn(okForContentType(responseContentType, responseBody).withStatus(HttpStatus.SC_ACCEPTED))); + + // Prepare test objects + final GetAllRequestBuilder getAll1 = service.getAllPeople().top(1); + final GetAllRequestBuilder getAll2 = service.getAllPeople().top(2).skip(1); + + // build batch request + final BatchResponse batchResponse = sut.addReadOperations(getAll1, getAll2).execute(destination); + + // Verify request body + final String requestContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + verify( + postRequestedFor(urlEqualTo(batchRequestUrl)) + .withoutHeader(HttpHeaders.ACCEPT) + .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(requestContentType)) + .withRequestBody(equalTo(requestBody))); + + // Test assertion: response object not null and healthy + assertThat(batchResponse.getResponseStatusCode()).isEqualTo(HttpStatus.SC_ACCEPTED); + assertThat(batchResponse.getResponseHeaders()).isNotEmpty(); + } + + @Test + void testBatchWithErrorReads() + { + // Read OData response json + final String requestBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchOnlyReadsRequest.txt"); + final String responseBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchOnlyReadsErrorResponse.txt"); + final String batchRequestUrl = String.format("%s%s", TrippinService.DEFAULT_SERVICE_PATH, "/$batch"); + + // Mocking OData response + final String responseContentType = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + stubFor( + post(urlEqualTo(batchRequestUrl)) + .willReturn(okForContentType(responseContentType, responseBody).withStatus(HttpStatus.SC_ACCEPTED))); + + // Prepare test objects + final GetAllRequestBuilder getAll1 = service.getAllPeople().top(1); + final GetAllRequestBuilder getAll2 = service.getAllPeople().top(2).skip(1); + + // build batch request + final BatchResponse batchResponse = sut.addReadOperations(getAll1, getAll2).execute(destination); + + assertThat(batchResponse.getReadResult(getAll1)).isNotEmpty(); + + assertThatExceptionOfType(ODataServiceErrorException.class) + .isThrownBy(() -> batchResponse.getReadResult(getAll2)) + .satisfies(e -> { + assertThat(e.getHttpCode()).isEqualTo(400); + assertThat(e.getOdataError().getODataCode()).isEqualTo("ZCU/100"); + }); + + // Verify request body + final String requestContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + verify( + postRequestedFor(urlEqualTo(batchRequestUrl)) + .withoutHeader(HttpHeaders.ACCEPT) + .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(requestContentType)) + .withRequestBody(equalTo(requestBody))); + + // Test assertion: response object not null and healthy + assertThat(batchResponse.getResponseStatusCode()).isEqualTo(HttpStatus.SC_ACCEPTED); + assertThat(batchResponse.getResponseHeaders()).isNotEmpty(); + } + + @Test + void testBatchWithReadsAndWrites() + { + // read response json + final String serverResponse = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchReadsAndWritesSuccessResponse.txt"); + final String batchRequestBody = + readResourceFileCrlf(ODataV4BatchReferenceServiceUnitTest.class, "BatchReadsAndWritesSuccessRequest.txt"); + final String batchRequestUrl = String.format("%s%s", TrippinService.DEFAULT_SERVICE_PATH, "/$batch"); + + final String responseContentType = + "multipart/mixed; boundary=batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef"; + + // Mocking S/4 Hana + stubFor( + post(urlEqualTo(batchRequestUrl)) + .willReturn(okForContentType(responseContentType, serverResponse).withStatus(HttpStatus.SC_ACCEPTED))); + + Person person1 = Person.builder().userName("JohnDoe1").firstName("John").build(); + Person person2 = Person.builder().userName("JohnDoe2").firstName("John").build(); + + // prepare test objects + final CreateRequestBuilder createPersonRequest1 = service.createPeople(person1); + final CreateRequestBuilder createPersonRequest2 = service.createPeople(person2); + final GetByKeyRequestBuilder getPersonByKeyRequest = service.getPeopleByKey("foo"); + + // build batch request + final BatchResponse batchResponse = + sut + .addChangeset(createPersonRequest1, createPersonRequest2) + .addReadOperations(getPersonByKeyRequest) + .execute(destination); + + assertThat(batchResponse.getResponseStatusCode()).isEqualTo(HttpStatus.SC_ACCEPTED); + assertThat(batchResponse.getResponseHeaders()).isNotEmpty(); + + // verify request body + final String requestContentType = "multipart/mixed;boundary=batch_00000000-0000-0000-0000-000000000001"; + verify( + postRequestedFor(urlEqualTo(batchRequestUrl)) + .withoutHeader(HttpHeaders.ACCEPT) + .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(requestContentType)) + .withRequestBody(equalTo(batchRequestBody))); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertyCustomSerializationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertyCustomSerializationTest.java new file mode 100644 index 0000000000..bde2f899d9 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertyCustomSerializationTest.java @@ -0,0 +1,175 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +class PropertyCustomSerializationTest +{ + private static final User USER = + new User( + LocalDate.of(1960, Month.MAY, 31).atTime(18, 0).atOffset(ZoneOffset.ofHours(1)), + LocalDate.of(2020, Month.FEBRUARY, 20).atStartOfDay().atZone(ZoneOffset.UTC), + LocalDate.of(2005, Month.DECEMBER, 24).atStartOfDay()); + + @EqualsAndHashCode( callSuper = true ) + @RequiredArgsConstructor + @AllArgsConstructor + @JsonAdapter( GsonVdmAdapterFactory.class ) + @JsonSerialize( using = JacksonVdmObjectSerializer.class ) + @JsonDeserialize( using = JacksonVdmObjectDeserializer.class ) + public static class User extends VdmEntity + { + @Getter + private final String entityCollection = "People"; + + @Getter + private final String odataType = "Example.User"; + + @Getter + private final Class type = User.class; + + @ElementName( "DateOfBirth" ) + private OffsetDateTime dateOfBirth; + + @ElementName( "LocaleTime" ) + @JsonSerialize( using = CustomZonedDateSerializer.class ) + @JsonDeserialize( using = CustomZonedDateDeserializer.class ) + @JsonAdapter( ZonedDateTimeAdapter.class ) + private ZonedDateTime localeTime; + + @ElementName( "RegistrationDate" ) + @JsonSerialize( using = CustomLocalDateSerializer.class ) + @JsonDeserialize( using = CustomLocalDateDeserializer.class ) + @JsonAdapter( LocalDateTimeAdapter.class ) + private LocalDateTime registrationDate; + } + + @Test + void testSerializationWithGson() + { + final String json = new Gson().toJson(USER); + final User actual = new Gson().fromJson(json, User.class); + assertThat(actual).isEqualTo(USER); + } + + @Test + void testSerializationWithJackson() + throws JsonProcessingException + { + final String json = new ObjectMapper().writeValueAsString(USER); + final User actual = new ObjectMapper().readValue(json, User.class); + assertThat(actual).isEqualTo(USER); + } + + // Custom serializers and deserializers + + public static class CustomLocalDateSerializer extends JsonSerializer + { + @Override + public void serialize( final LocalDateTime val, final JsonGenerator gen, final SerializerProvider serializers ) + throws IOException + { + gen.writeString(val.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + } + + public static class CustomZonedDateSerializer extends JsonSerializer + { + @Override + public void serialize( final ZonedDateTime val, final JsonGenerator gen, final SerializerProvider serializers ) + throws IOException + { + gen.writeString(val.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + } + + public static class CustomLocalDateDeserializer extends JsonDeserializer + { + @Override + public LocalDateTime deserialize( final JsonParser p, final DeserializationContext ctxt ) + throws IOException + { + return OffsetDateTime.parse(p.getValueAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).toLocalDateTime(); + } + } + + public static class CustomZonedDateDeserializer extends JsonDeserializer + { + @Override + public ZonedDateTime deserialize( final JsonParser p, final DeserializationContext ctxt ) + throws IOException + { + return OffsetDateTime.parse(p.getValueAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).toZonedDateTime(); + } + } + + public static class LocalDateTimeAdapter extends TypeAdapter + { + @Override + public void write( final JsonWriter out, final LocalDateTime value ) + throws IOException + { + out.value(value.atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + @Override + public LocalDateTime read( final JsonReader in ) + throws IOException + { + return OffsetDateTime.parse(in.nextString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).toLocalDateTime(); + } + } + + public static class ZonedDateTimeAdapter extends TypeAdapter + { + @Override + public void write( final JsonWriter out, final ZonedDateTime value ) + throws IOException + { + out.value(value.toOffsetDateTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + @Override + public ZonedDateTime read( final JsonReader in ) + throws IOException + { + return OffsetDateTime.parse(in.nextString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).toZonedDateTime(); + } + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertySerializationTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertySerializationTest.java new file mode 100644 index 0000000000..0466994ee8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/PropertySerializationTest.java @@ -0,0 +1,122 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.City; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Location; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.PersonGender; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; + +class PropertySerializationTest +{ + @Test + void testEnum() + { + final Person person = Person.builder().gender(PersonGender.FEMALE).firstName("Eve").build(); + final String json = new Gson().toJson(person); + final JsonElement actual = new Gson().toJsonTree(person); + assertThat(actual) + .isEqualTo( + JsonParser + .parseString( + "{\"FirstName\":\"Eve\",\"Gender\":\"Female\",\"@odata.type\":\"#Trippin.Person\",\"Friends\":[],\"Trips\":[]}")); + } + + @Test + void testComplexCollection() + { + final City city = new City(); + city.setName("Potsdam"); + city.setRegion("Nedlitz"); + city.setCountryRegion("Brandenburg"); + + final Location location = new Location(); + location.setAddress("Konrad-Zuse-Ring 10, 14469"); + location.setCity(city); + + final Person person = Person.builder().addressInfo(Arrays.asList(location)).build(); + + final JsonElement actual = new Gson().toJsonTree(person); + assertThat(actual).isEqualTo(JsonParser.parseString(""" + {"AddressInfo":[\ + {"Address":"Konrad-Zuse-Ring 10, 14469",\ + "City":{"Name":"Potsdam","CountryRegion":"Brandenburg","Region":"Nedlitz","@odata.type":"#Trippin.City"},\ + "@odata.type":"#Trippin.Location"}\ + ],"@odata.type":"#Trippin.Person"\ + ,"Friends":[],"Trips":[]}\ + """)); + // {"@odata.type":"#Trippin.Person","AddressInfo":[{"@odata.type":"#Trippin.Location","Address":"Konrad-Zuse-Ring 10, 14469","City":{"@odata.type":"#Trippin.City","Name":"Potsdam","CountryRegion":"Brandenburg","Region":"Nedlitz"}}],"Friends":[],"Trips":[]} + } + + @Test + void testPrimitiveCollection() + { + final String email = "eve@sap.com"; + final Person person = Person.builder().emails(Arrays.asList(email)).build(); + final JsonElement actual = new Gson().toJsonTree(person); + assertThat(actual) + .isEqualTo( + JsonParser + .parseString( + "{\"Emails\":[\"eve@sap.com\"],\"@odata.type\":\"#Trippin.Person\",\"Friends\":[],\"Trips\":[]}")); + } + + @Test + void testDateTimeGson() + { + final OffsetDateTime date1 = LocalDate.of(2020, 2, 20).atStartOfDay().atOffset(ZoneOffset.UTC); + final OffsetDateTime date2 = LocalDate.of(2020, 2, 20).atTime(3, 0).atOffset(ZoneOffset.ofHours(1)); + final Trip trip = Trip.builder().name("Trip1").startsAt(date1).endsAt(date2).build(); + + final String tripJson = new Gson().toJson(trip); + final JsonObject gsonObject = JsonParser.parseString(tripJson).getAsJsonObject(); + + final String startsAt = gsonObject.getAsJsonPrimitive("StartsAt").getAsString(); + assertThat(startsAt).isEqualTo("2020-02-20T00:00:00Z"); + + final String endsAt = gsonObject.getAsJsonPrimitive("EndsAt").getAsString(); + assertThat(endsAt).isEqualTo("2020-02-20T03:00:00+01:00"); + + final Trip tripGson = new Gson().fromJson(tripJson, Trip.class); + assertThat(tripGson).isEqualTo(trip); + assertThat(tripGson.getStartsAt()).isEqualTo(date1); + assertThat(tripGson.getEndsAt()).isEqualTo(date2); + } + + @Test + void testDateTimeJackson() + throws JsonProcessingException + { + final OffsetDateTime date1 = LocalDate.of(2020, 2, 20).atStartOfDay().atOffset(ZoneOffset.UTC); + final OffsetDateTime date2 = LocalDate.of(2020, 2, 20).atTime(3, 0).atOffset(ZoneOffset.ofHours(1)); + final Trip trip = Trip.builder().name("Trip1").startsAt(date1).endsAt(date2).build(); + + final ObjectMapper mapper = new ObjectMapper(); + final String tripJson = mapper.writeValueAsString(trip); + + final String startsAt = mapper.readTree(tripJson).get("StartsAt").textValue(); + assertThat(startsAt).isEqualTo("2020-02-20T00:00:00Z"); + + final String endsAt = mapper.readTree(tripJson).get("EndsAt").textValue(); + assertThat(endsAt).isEqualTo("2020-02-20T03:00:00+01:00"); + + final Trip tripJackson = mapper.readValue(tripJson, Trip.class); + assertThat(tripJackson).isEqualTo(trip); + assertThat(tripJackson.getStartsAt()).isEqualTo(date1); + assertThat(tripJackson.getEndsAt()).isEqualTo(date2); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/README.md b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/README.md new file mode 100644 index 0000000000..453cddc5da --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/README.md @@ -0,0 +1,13 @@ +# TripPin Service + +The tests in this package are based on the TripPin service originally (24.08.2023) found here: + +The modified version can be found here: `/datamodel/odata-v4/odata-v4-core/src/test/resources/unused_trippin.edmx` + +## Regenerating the Model + +Using the latest OData v4 Generator, take the metadata linked above and use a command similar to this: + +```bash +java -jar odata-v4-generator-cli-4.21.0.jar -i /datamodel/odata-v4/odata-v4-core/src/test/resources/ -o /datamodel/odata-v4/odata-v4-core/src/test/java/ -b TripPinRESTierService -f --use-odata-names --service-methods-per-entity-set --sap-copyright-header -p com.sap.cloud.sdk.datamodel.odatav4.referenceservice +``` \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SearchQueriesTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SearchQueriesTest.java new file mode 100644 index 0000000000..1e25fce5bb --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SearchQueriesTest.java @@ -0,0 +1,81 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.SearchExpression; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class SearchQueriesTest +{ + @Test + void testGetSearched() + { + final GetAllRequestBuilder search = new DefaultTrippinService().getAllPeople().search("Portland"); + assertThat(search.toRequest().getRelativeUri()).hasParameter("$search", "\"Portland\""); + } + + @Test + void testSpecialCharacters() + { + final GetAllRequestBuilder search = new DefaultTrippinService().getAllPeople().search("Hash #"); + assertThat(search.toRequest().getRelativeUri()).hasParameter("$search", "\"Hash #\""); + } + + @Test + void testGetSearchedPhrase() + { + final GetAllRequestBuilder search = new DefaultTrippinService().getAllPeople().search("United States"); + assertThat(search.toRequest().getRelativeUri()).hasParameter("$search", "\"United States\""); + } + + @Test + void testGetSearchedWithQuotesInString() + { + final GetAllRequestBuilder search = + new DefaultTrippinService().getAllPeople().search("Quoted \"string\""); + + // double quotes are escaped with simple backslash: Quote "string" -> Quoted \"string\" + assertThat(search.toRequest().getRelativeUri()).hasParameter("$search", "\"Quoted \\\"string\\\"\""); + } + + @Test + void testGetSearchedWithBackslashInString() + { + final GetAllRequestBuilder search = new DefaultTrippinService().getAllPeople().search("Escaped \\"); + + // backslashes (=escape character) are escaped with backslashes: Escaped \ -> Escaped \\ + assertThat(search.toRequest().getRelativeUri()).hasParameter("$search", "\"Escaped \\\\\""); + } + + @Test + void testSearchBooleanExpression() + { + + final GetAllRequestBuilder search = + new DefaultTrippinService().getAllPeople().search(SearchExpression.of("termA").or("termB").and("termC")); + + // double quotes are escaped with simple backslash: Quote "string" -> Quoted \"string\" + // (("termA" OR "termB") AND "termC") + assertThat(search.toRequest().getRelativeUri()) + .hasParameter("$search", "((\"termA\" OR \"termB\") AND \"termC\")"); + } + + @Test + void testSearchBooleanNot() + { + + final GetAllRequestBuilder search = + new DefaultTrippinService() + .getAllPeople() + .search(SearchExpression.of("termA").or("termB").and(SearchExpression.of("termC").not())); + + // double quotes are escaped with simple backslash: Quote "string" -> Quoted \"string\" + // (("termA" OR "termB") AND NOT "termC") + assertThat(search.toRequest().getRelativeUri()) + .hasParameter("$search", "((\"termA\" OR \"termB\") AND NOT \"termC\")"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SelectNestedComplexPropertiesTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SelectNestedComplexPropertiesTest.java new file mode 100644 index 0000000000..d4a660473f --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/SelectNestedComplexPropertiesTest.java @@ -0,0 +1,180 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.expression.FilterableBoolean; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.City; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Location; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.PlanItem; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Trip; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; + +class SelectNestedComplexPropertiesTest +{ + @Test + void testSelectComplexProperty() + { + final GetAllRequestBuilder query = + new DefaultTrippinService().getAllPeople().select(Person.FIRST_NAME, Person.LAST_NAME, Person.ADDRESS_INFO); + + final String expectedUnencodedQuery = "FirstName,LastName,AddressInfo"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$select", expectedUnencodedQuery); + } + + @Test + void testSelectNestedComplexProperty() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .select( + Person.FIRST_NAME, + Person.LAST_NAME, + Person.ADDRESS_INFO.select(Location.ADDRESS, Location.CITY)); + + final String expectedUnencodedQuery = "FirstName,LastName,AddressInfo/Address,AddressInfo/City"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$select", expectedUnencodedQuery); + } + + @Test + void testSelectSimplePropertyInNestedComplexProperty() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .select( + Person.FIRST_NAME, + Person.LAST_NAME, + Person.ADDRESS_INFO.select(Location.ADDRESS, Location.CITY.select(City.NAME))); + + final String expectedUnencodedQuery = "FirstName,LastName,AddressInfo/Address,AddressInfo/City/Name"; + + assertThat(query.toRequest().getRelativeUri()).hasParameter("$select", expectedUnencodedQuery); + } + + @Test + void testSelectComplexPropertyInNavigationProperty() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .select( + Person.TO_BEST_FRIEND + .select(Person.FIRST_NAME, Person.ADDRESS_INFO.select(Location.ADDRESS, Location.CITY))); + + assertThat(query.toRequest().getRelativeUri()) + .hasParameter("$expand", "BestFriend($select=FirstName,AddressInfo/Address,AddressInfo/City)"); + } + + @Test + void testAnyInNestedComplexPropertyCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .select(Person.FIRST_NAME, Person.LAST_NAME) + .filter(Person.ADDRESS_INFO.any(Location.ADDRESS.startsWith("Diagon Alley"))); + + assertThat(query.toRequest().getRelativeUri()) + .hasParameter("$select", "FirstName,LastName") + .hasParameter("$filter", "AddressInfo/any(a:startswith(a/Address,'Diagon Alley'))"); + } + + @Test + void testAnyInAnyPropertyCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter(Person.TO_FRIENDS.any(Person.ADDRESS_INFO.any(Location.ADDRESS.startsWith("Diagon Alley")))); + + assertThat(query.toRequest().getRelativeUri()) + .hasParameter("$filter", "Friends/any(a:a/AddressInfo/any(b:startswith(b/Address,'Diagon Alley')))"); + } + + @Test + void testAnyBesidesAllPropertyCollection() + { + final GetAllRequestBuilder query = + new DefaultTrippinService() + .getAllPeople() + .filter( + Person.TO_FRIENDS + .any(Person.FIRST_NAME.equalTo("Adam")) + .or(Person.TO_FRIENDS.all(Person.FIRST_NAME.equalTo("Eve")))); + + assertThat(query.toRequest().getRelativeUri()) + .hasParameter( + "$filter", + "(Friends/any(a:(a/FirstName eq 'Adam')) or Friends/all(a:(a/FirstName eq 'Eve')))"); + } + + @Test + void testLambdaParameterLevelsForSameEntity() + { + final FilterableBoolean lvl3 = Person.USER_NAME.equalTo("c1").or(Person.USER_NAME.equalTo("c2")); + + final FilterableBoolean lvl2 = + Person.USER_NAME.equalTo("b1").and(Person.TO_FRIENDS.any(lvl3)).and(Person.USER_NAME.equalTo("b2")); + + final FilterableBoolean lvl1 = + Person.USER_NAME.equalTo("a1").or(Person.TO_FRIENDS.all(lvl2)).or(Person.USER_NAME.equalTo("a2")); + + final FilterableBoolean lvl0 = + Person.USER_NAME.equalTo("1").and(Person.TO_FRIENDS.any(lvl1)).or(Person.USER_NAME.equalTo("2")); + + final GetAllRequestBuilder query = new DefaultTrippinService().getAllPeople().filter(lvl0); + + // Expectation for prefix usage: + // -> 1 + // a -> a1 + // b -> b1 + // c -> c1 + // c -> c2 + // b -> b1 + // a -> b1 + // -> 2 + assertThat(query.toRequest().getRelativeUri()) + .hasParameter( + "$filter", + "(((UserName eq '1') and Friends/any(a:(((a/UserName eq 'a1') or a/Friends/all(b:(((b/UserName eq 'b1') and b/Friends/any(c:((c/UserName eq 'c1') or (c/UserName eq 'c2')))) and (b/UserName eq 'b2')))) or (a/UserName eq 'a2')))) or (UserName eq '2'))"); + } + + @Test + void testLambdaParameterLevelsForDifferentEntities() + { + final FilterableBoolean lvl3 = + PlanItem.CONFIRMATION_CODE.equalTo("c1").or(PlanItem.CONFIRMATION_CODE.equalTo("c2")); + + final FilterableBoolean lvl2 = + Trip.NAME.equalTo("b1").and(Trip.TO_PLAN_ITEMS.any(lvl3)).and(Trip.NAME.equalTo("b2")); + + final FilterableBoolean lvl1 = + Person.USER_NAME.equalTo("a1").or(Person.TO_TRIPS.all(lvl2)).or(Person.USER_NAME.equalTo("a2")); + + final FilterableBoolean lvl0 = + Person.USER_NAME.equalTo("1").and(Person.TO_FRIENDS.any(lvl1)).and(Person.USER_NAME.equalTo("2")); + + final GetAllRequestBuilder query = new DefaultTrippinService().getAllPeople().filter(lvl0); + + // Expectation for prefix usage: + // -> 1 + // a -> a1 + // b -> b1 + // c -> c1 + // c -> c2 + // b -> b1 + // a -> b1 + // -> 2 + assertThat(query.toRequest().getRelativeUri()) + .hasParameter( + "$filter", + "(((UserName eq '1') and Friends/any(a:(((a/UserName eq 'a1') or a/Trips/all(b:(((b/Name eq 'b1') and b/PlanItems/any(c:((c/ConfirmationCode eq 'c1') or (c/ConfirmationCode eq 'c2')))) and (b/Name eq 'b2')))) or (a/UserName eq 'a2')))) and (UserName eq '2'))"); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/TripPinUtility.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/TripPinUtility.java new file mode 100644 index 0000000000..4f94ad6c41 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/TripPinUtility.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; + +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; + +public class TripPinUtility +{ + public static HttpDestination getDestination() + throws IOException + { + final HttpGet request = new HttpGet("https://services.odata.org/TripPinRESTierService/"); + final CloseableHttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); + final String serviceUrl = client.executeOpen(null, request, null).getLastHeader("Location").getValue(); + final Matcher tokenLookup = Pattern.compile("\\(S\\((.*?)\\)\\)").matcher(serviceUrl); + + assertThat(tokenLookup.find()).isTrue(); + + return DefaultHttpDestination + .builder("https://services.odata.org/TripPinRESTierService/(S(" + tokenLookup.group(1) + "))") + .build(); + } + + public static HttpDestination getDestinationRW() + throws IOException + { + final HttpGet request = new HttpGet("https://services.odata.org/V4/TripPinServiceRW/"); + final CloseableHttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); + final String serviceUrl = client.executeOpen(null, request, null).getLastHeader("Location").getValue(); + final Matcher tokenLookup = Pattern.compile("\\(S\\((.*?)\\)\\)").matcher(serviceUrl); + + assertThat(tokenLookup.find()).isTrue(); + + return DefaultHttpDestination + .builder("https://services.odata.org/V4/(S(" + tokenLookup.group(1) + "))") + .build(); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundActionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundActionTest.java new file mode 100644 index 0000000000..797f691de6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundActionTest.java @@ -0,0 +1,39 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.core.ActionResponseSingle; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueActionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; + +@Disabled( "Test runs against a v4 reference service on odata.org. Use it only to manually verify behaviour." ) +class UnboundActionTest +{ + + private static final TrippinService service = new DefaultTrippinService(); + private HttpDestination httpDestination; + + @BeforeEach + void configure() + throws IOException + { + httpDestination = TripPinUtility.getDestination(); + } + + @Test + void testActionWithoutParameters() + { + final SingleValueActionRequestBuilder builder = service.resetDataSource(); + final ActionResponseSingle actionResponse = builder.execute(httpDestination); + assertThat(actionResponse).isNotNull(); + assertThat(actionResponse.getResponseStatusCode()).isEqualTo(204); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundFunctionTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundFunctionTest.java new file mode 100644 index 0000000000..d1c66c61f0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/UnboundFunctionTest.java @@ -0,0 +1,47 @@ +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueFunctionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.DefaultTrippinService; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; + +@Disabled( "Test runs against a v4 reference service on odata.org. Use it only to manually verify behaviour." ) +class UnboundFunctionTest +{ + + private static final TrippinService service = new DefaultTrippinService(); + private HttpDestination httpDestination; + + @BeforeEach + void configure() + { + httpDestination = DefaultHttpDestination.builder("https://services.odata.org").build(); + } + + @Test + void testFunctionWithoutParameters() + { + final SingleValueFunctionRequestBuilder builder = service.getPersonWithMostFriends(); + final String expected = DefaultTrippinService.DEFAULT_SERVICE_PATH + "/GetPersonWithMostFriends"; + assertThat(builder.toRequest().getRelativeUri()).hasToString(expected); + assertThat(builder.execute(httpDestination)).isInstanceOf(Person.class); + } + + @Test + void testFunctionWithParameters() + { + final SingleValueFunctionRequestBuilder builder = service.getNearestAirport(33.0, -118.0); + final String expected = DefaultTrippinService.DEFAULT_SERVICE_PATH + "/GetNearestAirport(lat=33.0,lon=-118.0)"; + assertThat(builder.toRequest().getRelativeUri()).hasToString(expected); + assertThat(builder.execute(httpDestination)).isInstanceOf(Airport.class); + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airline.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airline.java new file mode 100644 index 0000000000..5d3a46baf7 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airline.java @@ -0,0 +1,178 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntitySet; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original entity name from the Odata EDM: Airline + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Airline extends VdmEntity implements VdmEntitySet +{ + + @Getter + private final java.lang.String odataType = "Trippin.Airline"; + /** + * Selector for all available fields of Airline. + * + */ + public final static SimpleProperty ALL_FIELDS = all(); + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: AirlineCode + *

+ * + * @return The airlineCode contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "AirlineCode" ) + private java.lang.String airlineCode; + public final static SimpleProperty.String AIRLINE_CODE = + new SimpleProperty.String(Airline.class, "AirlineCode"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @return The name contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Name" ) + private java.lang.String name; + public final static SimpleProperty.String NAME = new SimpleProperty.String(Airline.class, "Name"); + + @Nonnull + @Override + public Class getType() + { + return Airline.class; + } + + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: AirlineCode + *

+ * + * @param airlineCode + * The airlineCode to set. + */ + public void setAirlineCode( @Nullable final java.lang.String airlineCode ) + { + rememberChangedField("AirlineCode", this.airlineCode); + this.airlineCode = airlineCode; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @param name + * The name to set. + */ + public void setName( @Nullable final java.lang.String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + @Override + protected java.lang.String getEntityCollection() + { + return "Airlines"; + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + entityKey.addKeyProperty("AirlineCode", getAirlineCode()); + return entityKey; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("AirlineCode", getAirlineCode()); + values.put("Name", getName()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("AirlineCode") ) { + final Object value = values.remove("AirlineCode"); + if( (value == null) || (!value.equals(getAirlineCode())) ) { + setAirlineCode(((java.lang.String) value)); + } + } + if( values.containsKey("Name") ) { + final Object value = values.remove("Name"); + if( (value == null) || (!value.equals(getName())) ) { + setName(((java.lang.String) value)); + } + } + } + // structured properties + { + } + // navigation properties + { + } + super.fromMap(values); + } + + @Override + protected java.lang.String getDefaultServicePath() + { + return TrippinService.DEFAULT_SERVICE_PATH; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airport.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airport.java new file mode 100644 index 0000000000..f63591d32a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Airport.java @@ -0,0 +1,263 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntitySet; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original entity name from the Odata EDM: Airport + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Airport extends VdmEntity implements VdmEntitySet +{ + + @Getter + private final java.lang.String odataType = "Trippin.Airport"; + /** + * Selector for all available fields of Airport. + * + */ + public final static SimpleProperty ALL_FIELDS = all(); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @return The name contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Name" ) + private java.lang.String name; + public final static SimpleProperty.String NAME = new SimpleProperty.String(Airport.class, "Name"); + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: IcaoCode + *

+ * + * @return The icaoCode contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "IcaoCode" ) + private java.lang.String icaoCode; + public final static SimpleProperty.String ICAO_CODE = + new SimpleProperty.String(Airport.class, "IcaoCode"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: IataCode + *

+ * + * @return The iataCode contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "IataCode" ) + private java.lang.String iataCode; + public final static SimpleProperty.String IATA_CODE = + new SimpleProperty.String(Airport.class, "IataCode"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Location + *

+ * + * @return The location contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Location" ) + private AirportLocation location; + /** + * Use with available request builders to apply the Location complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single LOCATION = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single( + Airport.class, + "Location", + AirportLocation.class); + + @Nonnull + @Override + public Class getType() + { + return Airport.class; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @param name + * The name to set. + */ + public void setName( @Nullable final java.lang.String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: IcaoCode + *

+ * + * @param icaoCode + * The icaoCode to set. + */ + public void setIcaoCode( @Nullable final java.lang.String icaoCode ) + { + rememberChangedField("IcaoCode", this.icaoCode); + this.icaoCode = icaoCode; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: IataCode + *

+ * + * @param iataCode + * The iataCode to set. + */ + public void setIataCode( @Nullable final java.lang.String iataCode ) + { + rememberChangedField("IataCode", this.iataCode); + this.iataCode = iataCode; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Location + *

+ * + * @param location + * The location to set. + */ + public void setLocation( @Nullable final AirportLocation location ) + { + rememberChangedField("Location", this.location); + this.location = location; + } + + @Override + protected java.lang.String getEntityCollection() + { + return "Airports"; + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + entityKey.addKeyProperty("IcaoCode", getIcaoCode()); + return entityKey; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("Name", getName()); + values.put("IcaoCode", getIcaoCode()); + values.put("IataCode", getIataCode()); + values.put("Location", getLocation()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("Name") ) { + final Object value = values.remove("Name"); + if( (value == null) || (!value.equals(getName())) ) { + setName(((java.lang.String) value)); + } + } + if( values.containsKey("IcaoCode") ) { + final Object value = values.remove("IcaoCode"); + if( (value == null) || (!value.equals(getIcaoCode())) ) { + setIcaoCode(((java.lang.String) value)); + } + } + if( values.containsKey("IataCode") ) { + final Object value = values.remove("IataCode"); + if( (value == null) || (!value.equals(getIataCode())) ) { + setIataCode(((java.lang.String) value)); + } + } + } + // structured properties + { + if( values.containsKey("Location") ) { + final Object value = values.remove("Location"); + if( value instanceof Map ) { + if( getLocation() == null ) { + setLocation(new AirportLocation()); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + getLocation().fromMap(inputMap); + } + if( (value == null) && (getLocation() != null) ) { + setLocation(null); + } + } + } + // navigation properties + { + } + super.fromMap(values); + } + + @Override + protected java.lang.String getDefaultServicePath() + { + return TrippinService.DEFAULT_SERVICE_PATH; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/AirportLocation.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/AirportLocation.java new file mode 100644 index 0000000000..13ae1e0d0c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/AirportLocation.java @@ -0,0 +1,175 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmComplex; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original complex type name from the Odata EDM: AirportLocation + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class AirportLocation extends VdmComplex +{ + + @Getter + private final java.lang.String odataType = "Trippin.AirportLocation"; + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Address + *

+ * + * @return The address contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "Address" ) + private java.lang.String address; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String ADDRESS = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String( + AirportLocation.class, + "Address"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: City + *

+ * + * @return The city contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "City" ) + private City city; + /** + * Use with available request builders to apply the City complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single CITY = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single( + AirportLocation.class, + "City", + City.class); + + @Nonnull + @Override + public Class getType() + { + return AirportLocation.class; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("Address", getAddress()); + values.put("City", getCity()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("Address") ) { + final Object value = values.remove("Address"); + if( (value == null) || (!value.equals(getAddress())) ) { + setAddress(((java.lang.String) value)); + } + } + } + // structured properties + { + if( values.containsKey("City") ) { + final Object value = values.remove("City"); + if( value instanceof Map ) { + if( getCity() == null ) { + setCity(new City()); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + getCity().fromMap(inputMap); + } + if( (value == null) && (getCity() != null) ) { + setCity(null); + } + } + } + // navigation properties + { + } + super.fromMap(values); + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + return entityKey; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Address + *

+ * + * @param address + * The address to set. + */ + public void setAddress( @Nullable final java.lang.String address ) + { + rememberChangedField("Address", this.address); + this.address = address; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: City + *

+ * + * @param city + * The city to set. + */ + public void setCity( @Nullable final City city ) + { + rememberChangedField("City", this.city); + this.city = city; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Beverage.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Beverage.java new file mode 100644 index 0000000000..61ff3a4dc7 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Beverage.java @@ -0,0 +1,158 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmComplex; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original complex type name from the Odata EDM: Beverage + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Beverage extends VdmComplex +{ + + @Getter + private final java.lang.String odataType = "Trippin.Beverage"; + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @return The name contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "Name" ) + private java.lang.String name; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String NAME = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String(Beverage.class, "Name"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: IsAlcoholic + *

+ * + * @return The isAlcoholic contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "IsAlcoholic" ) + private java.lang.Boolean isAlcoholic; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.Boolean IS_ALCOHOLIC = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.Boolean(Beverage.class, "IsAlcoholic"); + + @Nonnull + @Override + public Class getType() + { + return Beverage.class; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("Name", getName()); + values.put("IsAlcoholic", getIsAlcoholic()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("Name") ) { + final Object value = values.remove("Name"); + if( (value == null) || (!value.equals(getName())) ) { + setName(((java.lang.String) value)); + } + } + if( values.containsKey("IsAlcoholic") ) { + final Object value = values.remove("IsAlcoholic"); + if( (value == null) || (!value.equals(getIsAlcoholic())) ) { + setIsAlcoholic(((java.lang.Boolean) value)); + } + } + } + // structured properties + { + } + // navigation properties + { + } + super.fromMap(values); + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + return entityKey; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @param name + * The name to set. + */ + public void setName( @Nullable final java.lang.String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: IsAlcoholic + *

+ * + * @param isAlcoholic + * The isAlcoholic to set. + */ + public void setIsAlcoholic( @Nullable final java.lang.Boolean isAlcoholic ) + { + rememberChangedField("IsAlcoholic", this.isAlcoholic); + this.isAlcoholic = isAlcoholic; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/City.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/City.java new file mode 100644 index 0000000000..5398dafc40 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/City.java @@ -0,0 +1,243 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmComplex; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original complex type name from the Odata EDM: City + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class City extends VdmComplex +{ + + @Getter + private final java.lang.String odataType = "Trippin.City"; + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @return The name contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "Name" ) + private java.lang.String name; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String NAME = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String(City.class, "Name"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: CountryRegion + *

+ * + * @return The countryRegion contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "CountryRegion" ) + private java.lang.String countryRegion; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String COUNTRY_REGION = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String(City.class, "CountryRegion"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Region + *

+ * + * @return The region contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "Region" ) + private java.lang.String region; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String REGION = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String(City.class, "Region"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: FamousBeverage + *

+ * + * @return The famousBeverage contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "FamousBeverage" ) + private Beverage famousBeverage; + /** + * Use with available request builders to apply the FamousBeverage complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single FAMOUS_BEVERAGE = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single( + City.class, + "FamousBeverage", + Beverage.class); + + @Nonnull + @Override + public Class getType() + { + return City.class; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("Name", getName()); + values.put("CountryRegion", getCountryRegion()); + values.put("Region", getRegion()); + values.put("FamousBeverage", getFamousBeverage()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("Name") ) { + final Object value = values.remove("Name"); + if( (value == null) || (!value.equals(getName())) ) { + setName(((java.lang.String) value)); + } + } + if( values.containsKey("CountryRegion") ) { + final Object value = values.remove("CountryRegion"); + if( (value == null) || (!value.equals(getCountryRegion())) ) { + setCountryRegion(((java.lang.String) value)); + } + } + if( values.containsKey("Region") ) { + final Object value = values.remove("Region"); + if( (value == null) || (!value.equals(getRegion())) ) { + setRegion(((java.lang.String) value)); + } + } + } + // structured properties + { + if( values.containsKey("FamousBeverage") ) { + final Object value = values.remove("FamousBeverage"); + if( value instanceof Map ) { + if( getFamousBeverage() == null ) { + setFamousBeverage(new Beverage()); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + getFamousBeverage().fromMap(inputMap); + } + if( (value == null) && (getFamousBeverage() != null) ) { + setFamousBeverage(null); + } + } + } + // navigation properties + { + } + super.fromMap(values); + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + return entityKey; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @param name + * The name to set. + */ + public void setName( @Nullable final java.lang.String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: CountryRegion + *

+ * + * @param countryRegion + * The countryRegion to set. + */ + public void setCountryRegion( @Nullable final java.lang.String countryRegion ) + { + rememberChangedField("CountryRegion", this.countryRegion); + this.countryRegion = countryRegion; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Region + *

+ * + * @param region + * The region to set. + */ + public void setRegion( @Nullable final java.lang.String region ) + { + rememberChangedField("Region", this.region); + this.region = region; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: FamousBeverage + *

+ * + * @param famousBeverage + * The famousBeverage to set. + */ + public void setFamousBeverage( @Nullable final Beverage famousBeverage ) + { + rememberChangedField("FamousBeverage", this.famousBeverage); + this.famousBeverage = famousBeverage; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Feature.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Feature.java new file mode 100644 index 0000000000..5d80fdce0c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Feature.java @@ -0,0 +1,72 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmEnumDeserializer; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmEnumSerializer; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +/** + *

+ * Original enum type name from the Odata EDM: Feature + *

+ * + */ +@JsonAdapter( GsonVdmAdapterFactory.class ) +@JsonSerialize( using = JacksonVdmEnumSerializer.class ) +@JsonDeserialize( using = JacksonVdmEnumDeserializer.class ) +public enum Feature implements VdmEnum +{ + + /** + * Feature1 + * + */ + FEATURE1("Feature1", 0L), + + /** + * Feature2 + * + */ + FEATURE2("Feature2", 1L), + + /** + * Feature3 + * + */ + FEATURE3("Feature3", 2L), + + /** + * Feature4 + * + */ + FEATURE4("Feature4", 3L); + + private final String name; + private final Long value; + + private Feature( final String enumName, final Long enumValue ) + { + name = enumName; + value = enumValue; + } + + @Override + public String getName() + { + return name; + } + + @Override + public Long getValue() + { + return value; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Location.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Location.java new file mode 100644 index 0000000000..36a0810f1a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Location.java @@ -0,0 +1,173 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmComplex; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original complex type name from the Odata EDM: Location + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Location extends VdmComplex +{ + + @Getter + private final java.lang.String odataType = "Trippin.Location"; + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Address + *

+ * + * @return The address contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "Address" ) + private java.lang.String address; + public final static com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String ADDRESS = + new com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty.String(Location.class, "Address"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: City + *

+ * + * @return The city contained in this {@link VdmComplex}. + */ + @Nullable + @ElementName( "City" ) + private City city; + /** + * Use with available request builders to apply the City complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single CITY = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single( + Location.class, + "City", + City.class); + + @Nonnull + @Override + public Class getType() + { + return Location.class; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("Address", getAddress()); + values.put("City", getCity()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("Address") ) { + final Object value = values.remove("Address"); + if( (value == null) || (!value.equals(getAddress())) ) { + setAddress(((java.lang.String) value)); + } + } + } + // structured properties + { + if( values.containsKey("City") ) { + final Object value = values.remove("City"); + if( value instanceof Map ) { + if( getCity() == null ) { + setCity(new City()); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + getCity().fromMap(inputMap); + } + if( (value == null) && (getCity() != null) ) { + setCity(null); + } + } + } + // navigation properties + { + } + super.fromMap(values); + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + return entityKey; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Address + *

+ * + * @param address + * The address to set. + */ + public void setAddress( @Nullable final java.lang.String address ) + { + rememberChangedField("Address", this.address); + this.address = address; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: City + *

+ * + * @param city + * The city to set. + */ + public void setCity( @Nullable final City city ) + { + rememberChangedField("City", this.city); + this.city = city; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Person.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Person.java new file mode 100644 index 0000000000..d9c5b18af3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Person.java @@ -0,0 +1,1246 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntitySet; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services.TrippinService; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + *

+ * Original entity name from the Odata EDM: Person + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Person extends VdmEntity implements VdmEntitySet +{ + + @Getter + private final java.lang.String odataType = "Trippin.Person"; + /** + * Selector for all available fields of Person. + * + */ + public final static SimpleProperty ALL_FIELDS = all(); + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: UserName + *

+ * + * @return The userName contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "UserName" ) + private java.lang.String userName; + public final static SimpleProperty.String USER_NAME = + new SimpleProperty.String(Person.class, "UserName"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: FirstName + *

+ * + * @return The firstName contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "FirstName" ) + private java.lang.String firstName; + public final static SimpleProperty.String FIRST_NAME = + new SimpleProperty.String(Person.class, "FirstName"); + /** + * Constraints: Nullable, Maximum length: 26 + *

+ * Original property name from the Odata EDM: LastName + *

+ * + * @return The lastName contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "LastName" ) + private java.lang.String lastName; + public final static SimpleProperty.String LAST_NAME = + new SimpleProperty.String(Person.class, "LastName"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: MiddleName + *

+ * + * @return The middleName contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "MiddleName" ) + private java.lang.String middleName; + public final static SimpleProperty.String MIDDLE_NAME = + new SimpleProperty.String(Person.class, "MiddleName"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Gender + *

+ * + * @return The gender contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Gender" ) + private PersonGender gender; + public final static SimpleProperty.Enum GENDER = + new SimpleProperty.Enum(Person.class, "Gender", "Trippin.PersonGender"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Age + *

+ * + * @return The age contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Age" ) + private Long age; + public final static SimpleProperty.NumericInteger AGE = + new SimpleProperty.NumericInteger(Person.class, "Age"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Emails + *

+ * + * @return The emails contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Emails" ) + private java.util.Collection emails; + public final static SimpleProperty.Collection EMAILS = + new SimpleProperty.Collection(Person.class, "Emails", java.lang.String.class); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: AddressInfo + *

+ * + * @return The addressInfo contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "AddressInfo" ) + private java.util.Collection addressInfo; + /** + * Use with available request builders to apply the AddressInfo complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Collection ADDRESS_INFO = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Collection( + Person.class, + "AddressInfo", + Location.class); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: HomeAddress + *

+ * + * @return The homeAddress contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "HomeAddress" ) + private Location homeAddress; + /** + * Use with available request builders to apply the HomeAddress complex property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single HOME_ADDRESS = + new com.sap.cloud.sdk.datamodel.odatav4.core.ComplexProperty.Single( + Person.class, + "HomeAddress", + Location.class); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: FavoriteFeature + *

+ * + * @return The favoriteFeature contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "FavoriteFeature" ) + private Feature favoriteFeature; + public final static SimpleProperty.Enum FAVORITE_FEATURE = + new SimpleProperty.Enum(Person.class, "FavoriteFeature", "Trippin.Feature"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Features + *

+ * + * @return The features contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Features" ) + private java.util.Collection features; + public final static SimpleProperty.Collection FEATURES = + new SimpleProperty.Collection(Person.class, "Features", Feature.class); + /** + * Navigation property Friends for Person to multiple Person. + * + */ + @ElementName( "Friends" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toFriends; + /** + * Navigation property BestFriend for Person to single Person. + * + */ + @ElementName( "BestFriend" ) + @Nullable + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private Person toBestFriend; + /** + * Navigation property Trips for Person to multiple Trip. + * + */ + @ElementName( "Trips" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toTrips; + /** + * Use with available request builders to apply the Friends navigation property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection TO_FRIENDS = + new com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection( + Person.class, + "Friends", + Person.class); + /** + * Use with available request builders to apply the BestFriend navigation property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Single TO_BEST_FRIEND = + new com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Single( + Person.class, + "BestFriend", + Person.class); + /** + * Use with available request builders to apply the Trips navigation property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection TO_TRIPS = + new com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection( + Person.class, + "Trips", + Trip.class); + + @Nonnull + @Override + public Class getType() + { + return Person.class; + } + + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: UserName + *

+ * + * @param userName + * The userName to set. + */ + public void setUserName( @Nullable final java.lang.String userName ) + { + rememberChangedField("UserName", this.userName); + this.userName = userName; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: FirstName + *

+ * + * @param firstName + * The firstName to set. + */ + public void setFirstName( @Nullable final java.lang.String firstName ) + { + rememberChangedField("FirstName", this.firstName); + this.firstName = firstName; + } + + /** + * Constraints: Nullable, Maximum length: 26 + *

+ * Original property name from the Odata EDM: LastName + *

+ * + * @param lastName + * The lastName to set. + */ + public void setLastName( @Nullable final java.lang.String lastName ) + { + rememberChangedField("LastName", this.lastName); + this.lastName = lastName; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: MiddleName + *

+ * + * @param middleName + * The middleName to set. + */ + public void setMiddleName( @Nullable final java.lang.String middleName ) + { + rememberChangedField("MiddleName", this.middleName); + this.middleName = middleName; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Gender + *

+ * + * @param gender + * The gender to set. + */ + public void setGender( @Nullable final PersonGender gender ) + { + rememberChangedField("Gender", this.gender); + this.gender = gender; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Age + *

+ * + * @param age + * The age to set. + */ + public void setAge( @Nullable final Long age ) + { + rememberChangedField("Age", this.age); + this.age = age; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Emails + *

+ * + * @param emails + * The emails to set. + */ + public void setEmails( @Nullable final java.util.Collection emails ) + { + rememberChangedField("Emails", this.emails); + this.emails = emails; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: AddressInfo + *

+ * + * @param addressInfo + * The addressInfo to set. + */ + public void setAddressInfo( @Nullable final java.util.Collection addressInfo ) + { + rememberChangedField("AddressInfo", this.addressInfo); + this.addressInfo = addressInfo; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: HomeAddress + *

+ * + * @param homeAddress + * The homeAddress to set. + */ + public void setHomeAddress( @Nullable final Location homeAddress ) + { + rememberChangedField("HomeAddress", this.homeAddress); + this.homeAddress = homeAddress; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: FavoriteFeature + *

+ * + * @param favoriteFeature + * The favoriteFeature to set. + */ + public void setFavoriteFeature( @Nullable final Feature favoriteFeature ) + { + rememberChangedField("FavoriteFeature", this.favoriteFeature); + this.favoriteFeature = favoriteFeature; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Features + *

+ * + * @param features + * The features to set. + */ + public void setFeatures( @Nullable final java.util.Collection features ) + { + rememberChangedField("Features", this.features); + this.features = features; + } + + @Override + protected java.lang.String getEntityCollection() + { + return "People"; + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + entityKey.addKeyProperty("UserName", getUserName()); + return entityKey; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("UserName", getUserName()); + values.put("FirstName", getFirstName()); + values.put("LastName", getLastName()); + values.put("MiddleName", getMiddleName()); + values.put("Gender", getGender()); + values.put("Age", getAge()); + values.put("Emails", getEmails()); + values.put("AddressInfo", getAddressInfo()); + values.put("HomeAddress", getHomeAddress()); + values.put("FavoriteFeature", getFavoriteFeature()); + values.put("Features", getFeatures()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("UserName") ) { + final Object value = values.remove("UserName"); + if( (value == null) || (!value.equals(getUserName())) ) { + setUserName(((java.lang.String) value)); + } + } + if( values.containsKey("FirstName") ) { + final Object value = values.remove("FirstName"); + if( (value == null) || (!value.equals(getFirstName())) ) { + setFirstName(((java.lang.String) value)); + } + } + if( values.containsKey("LastName") ) { + final Object value = values.remove("LastName"); + if( (value == null) || (!value.equals(getLastName())) ) { + setLastName(((java.lang.String) value)); + } + } + if( values.containsKey("MiddleName") ) { + final Object value = values.remove("MiddleName"); + if( (value == null) || (!value.equals(getMiddleName())) ) { + setMiddleName(((java.lang.String) value)); + } + } + if( values.containsKey("Gender") ) { + final Object value = values.remove("Gender"); + if( (value instanceof java.lang.String) || (value == null) ) { + final PersonGender gender = VdmEnum.getConstant(PersonGender.class, ((java.lang.String) value)); + if( !Objects.equals(gender, getGender()) ) { + setGender(gender); + } + } + } + if( values.containsKey("Age") ) { + final Object value = values.remove("Age"); + if( (value == null) || (!value.equals(getAge())) ) { + setAge(((Long) value)); + } + } + if( values.containsKey("Emails") ) { + final Object value = values.remove("Emails"); + if( value instanceof Iterable ) { + final LinkedList emails = new LinkedList(); + for( Object item : ((Iterable) value) ) { + emails.add(((java.lang.String) item)); + } + setEmails(emails); + } + } + if( values.containsKey("FavoriteFeature") ) { + final Object value = values.remove("FavoriteFeature"); + if( (value instanceof java.lang.String) || (value == null) ) { + final Feature favoriteFeature = VdmEnum.getConstant(Feature.class, ((java.lang.String) value)); + if( !Objects.equals(favoriteFeature, getFavoriteFeature()) ) { + setFavoriteFeature(favoriteFeature); + } + } + } + if( values.containsKey("Features") ) { + final Object value = values.remove("Features"); + if( (value == null) && (getFeatures() != null) ) { + setFeatures(null); + } + if( value instanceof Iterable ) { + final LinkedList features = new LinkedList(); + for( Object item : ((Iterable) value) ) { + if( item instanceof java.lang.String ) { + final Feature enumConstant = VdmEnum.getConstant(Feature.class, ((java.lang.String) item)); + features.add(enumConstant); + } + } + if( !Objects.equals(features, getFeatures()) ) { + setFeatures(features); + } + } + } + } + // structured properties + { + if( values.containsKey("AddressInfo") ) { + final Object value = values.remove("AddressInfo"); + if( value instanceof Iterable ) { + final LinkedList addressInfo = new LinkedList(); + for( Object properties : ((Iterable) value) ) { + if( properties instanceof Map ) { + final Location item = new Location(); + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + item.fromMap(inputMap); + addressInfo.add(item); + } + } + setAddressInfo(addressInfo); + } + if( (value == null) && (getAddressInfo() != null) ) { + setAddressInfo(null); + } + } + if( values.containsKey("HomeAddress") ) { + final Object value = values.remove("HomeAddress"); + if( value instanceof Map ) { + if( getHomeAddress() == null ) { + setHomeAddress(new Location()); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + getHomeAddress().fromMap(inputMap); + } + if( (value == null) && (getHomeAddress() != null) ) { + setHomeAddress(null); + } + } + } + // navigation properties + { + if( (values).containsKey("Friends") ) { + final Object value = (values).remove("Friends"); + if( value instanceof Iterable ) { + if( toFriends == null ) { + toFriends = Lists.newArrayList(); + } else { + toFriends = Lists.newArrayList(toFriends); + } + int i = 0; + for( Object item : ((Iterable) value) ) { + if( !(item instanceof Map) ) { + continue; + } + Person entity; + if( toFriends.size() > i ) { + entity = toFriends.get(i); + } else { + entity = new Person(); + toFriends.add(entity); + } + i = (i + 1); + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) item); + entity.fromMap(inputMap); + } + } + } + if( (values).containsKey("BestFriend") ) { + final Object value = (values).remove("BestFriend"); + if( value instanceof Map ) { + if( toBestFriend == null ) { + toBestFriend = new Person(); + } + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) value); + toBestFriend.fromMap(inputMap); + } + } + if( (values).containsKey("Trips") ) { + final Object value = (values).remove("Trips"); + if( value instanceof Iterable ) { + if( toTrips == null ) { + toTrips = Lists.newArrayList(); + } else { + toTrips = Lists.newArrayList(toTrips); + } + int i = 0; + for( Object item : ((Iterable) value) ) { + if( !(item instanceof Map) ) { + continue; + } + Trip entity; + if( toTrips.size() > i ) { + entity = toTrips.get(i); + } else { + entity = new Trip(); + toTrips.add(entity); + } + i = (i + 1); + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) item); + entity.fromMap(inputMap); + } + } + } + } + super.fromMap(values); + } + + @Override + protected java.lang.String getDefaultServicePath() + { + return TrippinService.DEFAULT_SERVICE_PATH; + } + + @Nonnull + @Override + protected Map toMapOfNavigationProperties() + { + final Map values = super.toMapOfNavigationProperties(); + if( toFriends != null ) { + (values).put("Friends", toFriends); + } + if( toBestFriend != null ) { + (values).put("BestFriend", toBestFriend); + } + if( toTrips != null ) { + (values).put("Trips", toTrips); + } + return values; + } + + /** + * Retrieval of associated Person entities (one to many). This corresponds to the OData navigation property + * Friends. + *

+ * If the navigation property for an entity Person has not been resolved yet, this method will not + * query further information. Instead its Option result state will be empty. + * + * @return If the information for navigation property Friends is already loaded, the result will contain the + * Person entities. If not, an Option with result state empty is returned. + */ + @Nonnull + public Option> getFriendsIfPresent() + { + return Option.of(toFriends); + } + + /** + * Overwrites the list of associated Person entities for the loaded navigation property Friends. + *

+ * If the navigation property Friends of a queried Person is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param value + * List of Person entities. + */ + public void setFriends( @Nonnull final List value ) + { + if( toFriends == null ) { + toFriends = Lists.newArrayList(); + } + toFriends.clear(); + toFriends.addAll(value); + } + + /** + * Adds elements to the list of associated Person entities. This corresponds to the OData navigation property + * Friends. + *

+ * If the navigation property Friends of a queried Person is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param entity + * Array of Person entities. + */ + public void addFriends( Person... entity ) + { + if( toFriends == null ) { + toFriends = Lists.newArrayList(); + } + toFriends.addAll(Lists.newArrayList(entity)); + } + + /** + * Retrieval of associated Person entity (one to one). This corresponds to the OData navigation property + * BestFriend. + *

+ * If the navigation property for an entity Person has not been resolved yet, this method will not + * query further information. Instead its Option result state will be empty. + * + * @return If the information for navigation property BestFriend is already loaded, the result will contain + * the Person entity. If not, an Option with result state empty is + * returned. + */ + @Nonnull + public Option getBestFriendIfPresent() + { + return Option.of(toBestFriend); + } + + /** + * Overwrites the associated Person entity for the loaded navigation property BestFriend. + * + * @param value + * New Person entity. + */ + public void setBestFriend( final Person value ) + { + toBestFriend = value; + } + + /** + * Retrieval of associated Trip entities (one to many). This corresponds to the OData navigation property + * Trips. + *

+ * If the navigation property for an entity Person has not been resolved yet, this method will not + * query further information. Instead its Option result state will be empty. + * + * @return If the information for navigation property Trips is already loaded, the result will contain the + * Trip entities. If not, an Option with result state empty is returned. + */ + @Nonnull + public Option> getTripsIfPresent() + { + return Option.of(toTrips); + } + + /** + * Overwrites the list of associated Trip entities for the loaded navigation property Trips. + *

+ * If the navigation property Trips of a queried Person is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param value + * List of Trip entities. + */ + public void setTrips( @Nonnull final List value ) + { + if( toTrips == null ) { + toTrips = Lists.newArrayList(); + } + toTrips.clear(); + toTrips.addAll(value); + } + + /** + * Adds elements to the list of associated Trip entities. This corresponds to the OData navigation property + * Trips. + *

+ * If the navigation property Trips of a queried Person is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param entity + * Array of Trip entities. + */ + public void addTrips( Trip... entity ) + { + if( toTrips == null ) { + toTrips = Lists.newArrayList(); + } + toTrips.addAll(Lists.newArrayList(entity)); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle + getFavoriteAirline() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle( + Person.class, + Airline.class, + "Trippin.GetFavoriteAirline", + parameters); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @param userName + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: userName + *

+ * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToCollection + getFriendsTrips( @Nonnull final java.lang.String userName ) + { + final Map parameters = new HashMap(); + parameters.put("userName", userName); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToCollection( + Person.class, + Trip.class, + "Trippin.GetFriendsTrips", + parameters); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle isHappy() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle( + Person.class, + Boolean.class, + "Trippin.IsHappy", + parameters); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @param really + * Constraints: Nullable + *

+ * Original parameter name from the Odata EDM: really + *

+ * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle isHappy( + @Nullable final Boolean really ) + { + final Map parameters = new HashMap(); + parameters.put("really", really); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingle( + Person.class, + Boolean.class, + "Trippin.IsHappy", + parameters); + } + + /** + * Function that can be applied to a collection of entities of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to a collection of entities of this + * class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToSingle + areAllFriends() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToSingle( + Person.class, + Boolean.class, + "Trippin.AreAllFriends", + parameters); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingleEntity.Composable + worstFriend() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToSingleEntity.Composable( + Person.class, + Person.class, + "Trippin.WorstFriend", + parameters); + } + + /** + * Function that can be applied to a collection of entities of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to a collection of entities of this + * class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToSingleEntity.Composable + mostPopularPerson() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToSingleEntity.Composable( + Person.class, + Person.class, + "Trippin.MostPopularPerson", + parameters); + } + + /** + * Function that can be applied to a collection of entities of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to a collection of entities of this + * class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToCollectionEntity.Composable + mostPopularPersons() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.CollectionToCollectionEntity.Composable( + Person.class, + Person.class, + "Trippin.MostPopularPersons", + parameters); + } + + /** + * Action that can be applied to any entity object of this class. + *

+ * + * @param lastName + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: lastName + *

+ * @return Action object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle updateLastName( + @Nonnull final java.lang.String lastName ) + { + final Map parameters = new HashMap(); + parameters.put("lastName", lastName); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle( + Person.class, + Boolean.class, + "Trippin.UpdateLastName", + parameters); + } + + /** + * Action that can be applied to any entity object of this class. + *

+ * + * @param tripId + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: tripId + *

+ * @param userName + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: userName + *

+ * @return Action object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle + shareTrip( @Nonnull final java.lang.String userName, @Nonnull final Integer tripId ) + { + final Map parameters = new HashMap(); + parameters.put("userName", userName); + parameters.put("tripId", tripId); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle( + Person.class, + Void.class, + "Trippin.ShareTrip", + parameters); + } + + /** + * Action that can be applied to any entity object of this class. + *

+ * + * @return Action object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle makeHappy() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle( + Person.class, + Void.class, + "Trippin.MakeHappy", + parameters); + } + + /** + * Action that can be applied to any entity object of this class. + *

+ * + * @param very + * Constraints: Nullable + *

+ * Original parameter name from the Odata EDM: very + *

+ * @return Action object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle makeUnhappy( + @Nullable final Boolean very ) + { + final Map parameters = new HashMap(); + parameters.put("very", very); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToSingle( + Person.class, + Person.class, + "Trippin.MakeUnhappy", + parameters); + } + + /** + * Action that can be applied to a collection of entities of this class. + *

+ * + * @return Action object prepared with the given parameters to be applied to a collection of entities of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.CollectionToSingle makeAllHappy() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.CollectionToSingle( + Person.class, + Void.class, + "Trippin.MakeAllHappy", + parameters); + } + + /** + * Action that can be applied to any entity object of this class. + *

+ * + * @param subject + * Constraints: Nullable + *

+ * Original parameter name from the Odata EDM: subject + *

+ * @return Action object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyAction(thisAction)} API. + */ + @Nonnull + public static com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToCollection sendMail( + @Nullable final java.lang.String subject ) + { + final Map parameters = new HashMap(); + parameters.put("subject", subject); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundAction.SingleToCollection( + Person.class, + Person.class, + "Trippin.sendMail", + parameters); + } + + /** + * Helper class to allow for fluent creation of Person instances. + * + */ + public final static class PersonBuilder + { + + private List toFriends = Lists.newArrayList(); + private Person toBestFriend; + private List toTrips = Lists.newArrayList(); + + private Person.PersonBuilder toFriends( final List value ) + { + toFriends.addAll(value); + return this; + } + + /** + * Navigation property Friends for Person to multiple Person. + * + * @param value + * The Persons to build this Person with. + * @return This Builder to allow for a fluent interface. + */ + @Nonnull + public Person.PersonBuilder friends( Person... value ) + { + return toFriends(Lists.newArrayList(value)); + } + + private Person.PersonBuilder toBestFriend( final Person value ) + { + toBestFriend = value; + return this; + } + + /** + * Navigation property BestFriend for Person to single Person. + * + * @param value + * The Person to build this Person with. + * @return This Builder to allow for a fluent interface. + */ + @Nonnull + public Person.PersonBuilder bestFriend( final Person value ) + { + return toBestFriend(value); + } + + private Person.PersonBuilder toTrips( final List value ) + { + toTrips.addAll(value); + return this; + } + + /** + * Navigation property Trips for Person to multiple Trip. + * + * @param value + * The Trips to build this Person with. + * @return This Builder to allow for a fluent interface. + */ + @Nonnull + public Person.PersonBuilder trips( Trip... value ) + { + return toTrips(Lists.newArrayList(value)); + } + + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PersonGender.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PersonGender.java new file mode 100644 index 0000000000..d581b02c5d --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PersonGender.java @@ -0,0 +1,66 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmEnumDeserializer; +import com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmEnumSerializer; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEnum; + +/** + *

+ * Original enum type name from the Odata EDM: PersonGender + *

+ * + */ +@JsonAdapter( GsonVdmAdapterFactory.class ) +@JsonSerialize( using = JacksonVdmEnumSerializer.class ) +@JsonDeserialize( using = JacksonVdmEnumDeserializer.class ) +public enum PersonGender implements VdmEnum +{ + + /** + * Male + * + */ + MALE("Male", 0L), + + /** + * Female + * + */ + FEMALE("Female", 1L), + + /** + * Unknown + * + */ + UNKNOWN("Unknown", 2L); + + private final String name; + private final Long value; + + private PersonGender( final String enumName, final Long enumValue ) + { + name = enumName; + value = enumValue; + } + + @Override + public String getName() + { + return name; + } + + @Override + public Long getValue() + { + return value; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PlanItem.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PlanItem.java new file mode 100644 index 0000000000..731f0b86aa --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/PlanItem.java @@ -0,0 +1,286 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.result.ElementName; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + *

+ * Original entity name from the Odata EDM: PlanItem + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class PlanItem extends VdmEntity +{ + + @Getter + private final java.lang.String odataType = "Trippin.PlanItem"; + /** + * Selector for all available fields of PlanItem. + * + */ + public final static SimpleProperty ALL_FIELDS = all(); + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: PlanItemId + *

+ * + * @return The planItemId contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "PlanItemId" ) + private Integer planItemId; + public final static SimpleProperty.NumericInteger PLAN_ITEM_ID = + new SimpleProperty.NumericInteger(PlanItem.class, "PlanItemId"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: ConfirmationCode + *

+ * + * @return The confirmationCode contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "ConfirmationCode" ) + private java.lang.String confirmationCode; + public final static SimpleProperty.String CONFIRMATION_CODE = + new SimpleProperty.String(PlanItem.class, "ConfirmationCode"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: StartsAt + *

+ * + * @return The startsAt contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "StartsAt" ) + private OffsetDateTime startsAt; + public final static SimpleProperty.DateTime STARTS_AT = + new SimpleProperty.DateTime(PlanItem.class, "StartsAt"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: EndsAt + *

+ * + * @return The endsAt contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "EndsAt" ) + private OffsetDateTime endsAt; + public final static SimpleProperty.DateTime ENDS_AT = + new SimpleProperty.DateTime(PlanItem.class, "EndsAt"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Duration + *

+ * + * @return The duration contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Duration" ) + private BigDecimal duration; + public final static SimpleProperty.Duration DURATION = + new SimpleProperty.Duration(PlanItem.class, "Duration"); + + @Nonnull + @Override + public Class getType() + { + return PlanItem.class; + } + + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: PlanItemId + *

+ * + * @param planItemId + * The planItemId to set. + */ + public void setPlanItemId( @Nullable final Integer planItemId ) + { + rememberChangedField("PlanItemId", this.planItemId); + this.planItemId = planItemId; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: ConfirmationCode + *

+ * + * @param confirmationCode + * The confirmationCode to set. + */ + public void setConfirmationCode( @Nullable final java.lang.String confirmationCode ) + { + rememberChangedField("ConfirmationCode", this.confirmationCode); + this.confirmationCode = confirmationCode; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: StartsAt + *

+ * + * @param startsAt + * The startsAt to set. + */ + public void setStartsAt( @Nullable final OffsetDateTime startsAt ) + { + rememberChangedField("StartsAt", this.startsAt); + this.startsAt = startsAt; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: EndsAt + *

+ * + * @param endsAt + * The endsAt to set. + */ + public void setEndsAt( @Nullable final OffsetDateTime endsAt ) + { + rememberChangedField("EndsAt", this.endsAt); + this.endsAt = endsAt; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Duration + *

+ * + * @param duration + * The duration to set. + */ + public void setDuration( @Nullable final BigDecimal duration ) + { + rememberChangedField("Duration", this.duration); + this.duration = duration; + } + + @Override + protected java.lang.String getEntityCollection() + { + return "PlanItems"; + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + entityKey.addKeyProperty("PlanItemId", getPlanItemId()); + return entityKey; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("PlanItemId", getPlanItemId()); + values.put("ConfirmationCode", getConfirmationCode()); + values.put("StartsAt", getStartsAt()); + values.put("EndsAt", getEndsAt()); + values.put("Duration", getDuration()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("PlanItemId") ) { + final Object value = values.remove("PlanItemId"); + if( (value == null) || (!value.equals(getPlanItemId())) ) { + setPlanItemId(((Integer) value)); + } + } + if( values.containsKey("ConfirmationCode") ) { + final Object value = values.remove("ConfirmationCode"); + if( (value == null) || (!value.equals(getConfirmationCode())) ) { + setConfirmationCode(((java.lang.String) value)); + } + } + if( values.containsKey("StartsAt") ) { + final Object value = values.remove("StartsAt"); + if( (value == null) || (!value.equals(getStartsAt())) ) { + setStartsAt(((OffsetDateTime) value)); + } + } + if( values.containsKey("EndsAt") ) { + final Object value = values.remove("EndsAt"); + if( (value == null) || (!value.equals(getEndsAt())) ) { + setEndsAt(((OffsetDateTime) value)); + } + } + if( values.containsKey("Duration") ) { + final Object value = values.remove("Duration"); + if( (value == null) || (!value.equals(getDuration())) ) { + setDuration(((BigDecimal) value)); + } + } + } + // structured properties + { + } + // navigation properties + { + } + super.fromMap(values); + } + + @Nonnull + @Override + protected Map toMapOfNavigationProperties() + { + final Map values = super.toMapOfNavigationProperties(); + return values; + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Trip.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Trip.java new file mode 100644 index 0000000000..729f984bec --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/namespaces/trippin/Trip.java @@ -0,0 +1,560 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gson.annotations.JsonAdapter; +import com.sap.cloud.sdk.datamodel.odata.client.request.ODataEntityKey; +import com.sap.cloud.sdk.datamodel.odatav4.core.SimpleProperty; +import com.sap.cloud.sdk.datamodel.odatav4.core.VdmEntity; +import com.sap.cloud.sdk.result.ElementName; + +import io.vavr.control.Option; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + *

+ * Original entity name from the Odata EDM: Trip + *

+ * + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString( doNotUseGetters = true, callSuper = true ) +@EqualsAndHashCode( doNotUseGetters = true, callSuper = true ) +@JsonAdapter( com.sap.cloud.sdk.datamodel.odatav4.adapter.GsonVdmAdapterFactory.class ) +@JsonSerialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectSerializer.class ) +@JsonDeserialize( using = com.sap.cloud.sdk.datamodel.odatav4.adapter.JacksonVdmObjectDeserializer.class ) +public class Trip extends VdmEntity +{ + + @Getter + private final java.lang.String odataType = "Trippin.Trip"; + /** + * Selector for all available fields of Trip. + * + */ + public final static SimpleProperty ALL_FIELDS = all(); + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: TripId + *

+ * + * @return The tripId contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "TripId" ) + private Integer tripId; + public final static SimpleProperty.NumericInteger TRIP_ID = + new SimpleProperty.NumericInteger(Trip.class, "TripId"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: ShareId + *

+ * + * @return The shareId contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "ShareId" ) + private UUID shareId; + public final static SimpleProperty.Guid SHARE_ID = new SimpleProperty.Guid(Trip.class, "ShareId"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @return The name contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Name" ) + private java.lang.String name; + public final static SimpleProperty.String NAME = new SimpleProperty.String(Trip.class, "Name"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Budget + *

+ * + * @return The budget contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Budget" ) + private Float budget; + public final static SimpleProperty.NumericDecimal BUDGET = + new SimpleProperty.NumericDecimal(Trip.class, "Budget"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Description + *

+ * + * @return The description contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Description" ) + private java.lang.String description; + public final static SimpleProperty.String DESCRIPTION = + new SimpleProperty.String(Trip.class, "Description"); + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Tags + *

+ * + * @return The tags contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "Tags" ) + private java.util.Collection tags; + public final static SimpleProperty.Collection TAGS = + new SimpleProperty.Collection(Trip.class, "Tags", java.lang.String.class); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: StartsAt + *

+ * + * @return The startsAt contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "StartsAt" ) + private OffsetDateTime startsAt; + public final static SimpleProperty.DateTime STARTS_AT = + new SimpleProperty.DateTime(Trip.class, "StartsAt"); + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: EndsAt + *

+ * + * @return The endsAt contained in this {@link VdmEntity}. + */ + @Nullable + @ElementName( "EndsAt" ) + private OffsetDateTime endsAt; + public final static SimpleProperty.DateTime ENDS_AT = new SimpleProperty.DateTime(Trip.class, "EndsAt"); + /** + * Navigation property PlanItems for Trip to multiple PlanItem. + * + */ + @ElementName( "PlanItems" ) + @Getter( AccessLevel.NONE ) + @Setter( AccessLevel.NONE ) + private List toPlanItems; + /** + * Use with available request builders to apply the PlanItems navigation property to query operations. + * + */ + public final static com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection TO_PLAN_ITEMS = + new com.sap.cloud.sdk.datamodel.odatav4.core.NavigationProperty.Collection( + Trip.class, + "PlanItems", + PlanItem.class); + + @Nonnull + @Override + public Class getType() + { + return Trip.class; + } + + /** + * (Key Field) Constraints: Not nullable + *

+ * Original property name from the Odata EDM: TripId + *

+ * + * @param tripId + * The tripId to set. + */ + public void setTripId( @Nullable final Integer tripId ) + { + rememberChangedField("TripId", this.tripId); + this.tripId = tripId; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: ShareId + *

+ * + * @param shareId + * The shareId to set. + */ + public void setShareId( @Nullable final UUID shareId ) + { + rememberChangedField("ShareId", this.shareId); + this.shareId = shareId; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Name + *

+ * + * @param name + * The name to set. + */ + public void setName( @Nullable final java.lang.String name ) + { + rememberChangedField("Name", this.name); + this.name = name; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: Budget + *

+ * + * @param budget + * The budget to set. + */ + public void setBudget( @Nullable final Float budget ) + { + rememberChangedField("Budget", this.budget); + this.budget = budget; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Description + *

+ * + * @param description + * The description to set. + */ + public void setDescription( @Nullable final java.lang.String description ) + { + rememberChangedField("Description", this.description); + this.description = description; + } + + /** + * Constraints: Nullable + *

+ * Original property name from the Odata EDM: Tags + *

+ * + * @param tags + * The tags to set. + */ + public void setTags( @Nullable final java.util.Collection tags ) + { + rememberChangedField("Tags", this.tags); + this.tags = tags; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: StartsAt + *

+ * + * @param startsAt + * The startsAt to set. + */ + public void setStartsAt( @Nullable final OffsetDateTime startsAt ) + { + rememberChangedField("StartsAt", this.startsAt); + this.startsAt = startsAt; + } + + /** + * Constraints: Not nullable + *

+ * Original property name from the Odata EDM: EndsAt + *

+ * + * @param endsAt + * The endsAt to set. + */ + public void setEndsAt( @Nullable final OffsetDateTime endsAt ) + { + rememberChangedField("EndsAt", this.endsAt); + this.endsAt = endsAt; + } + + @Override + protected java.lang.String getEntityCollection() + { + return "Trips"; + } + + @Nonnull + @Override + protected ODataEntityKey getKey() + { + final ODataEntityKey entityKey = super.getKey(); + entityKey.addKeyProperty("TripId", getTripId()); + return entityKey; + } + + @Nonnull + @Override + protected Map toMapOfFields() + { + final Map values = super.toMapOfFields(); + values.put("TripId", getTripId()); + values.put("ShareId", getShareId()); + values.put("Name", getName()); + values.put("Budget", getBudget()); + values.put("Description", getDescription()); + values.put("Tags", getTags()); + values.put("StartsAt", getStartsAt()); + values.put("EndsAt", getEndsAt()); + return values; + } + + @Override + protected void fromMap( final Map inputValues ) + { + final Map values = Maps.newHashMap(inputValues); + // simple properties + { + if( values.containsKey("TripId") ) { + final Object value = values.remove("TripId"); + if( (value == null) || (!value.equals(getTripId())) ) { + setTripId(((Integer) value)); + } + } + if( values.containsKey("ShareId") ) { + final Object value = values.remove("ShareId"); + if( (value == null) || (!value.equals(getShareId())) ) { + setShareId(((UUID) value)); + } + } + if( values.containsKey("Name") ) { + final Object value = values.remove("Name"); + if( (value == null) || (!value.equals(getName())) ) { + setName(((java.lang.String) value)); + } + } + if( values.containsKey("Budget") ) { + final Object value = values.remove("Budget"); + if( (value == null) || (!value.equals(getBudget())) ) { + setBudget(((Float) value)); + } + } + if( values.containsKey("Description") ) { + final Object value = values.remove("Description"); + if( (value == null) || (!value.equals(getDescription())) ) { + setDescription(((java.lang.String) value)); + } + } + if( values.containsKey("Tags") ) { + final Object value = values.remove("Tags"); + if( value instanceof Iterable ) { + final LinkedList tags = new LinkedList(); + for( Object item : ((Iterable) value) ) { + tags.add(((java.lang.String) item)); + } + setTags(tags); + } + } + if( values.containsKey("StartsAt") ) { + final Object value = values.remove("StartsAt"); + if( (value == null) || (!value.equals(getStartsAt())) ) { + setStartsAt(((OffsetDateTime) value)); + } + } + if( values.containsKey("EndsAt") ) { + final Object value = values.remove("EndsAt"); + if( (value == null) || (!value.equals(getEndsAt())) ) { + setEndsAt(((OffsetDateTime) value)); + } + } + } + // structured properties + { + } + // navigation properties + { + if( (values).containsKey("PlanItems") ) { + final Object value = (values).remove("PlanItems"); + if( value instanceof Iterable ) { + if( toPlanItems == null ) { + toPlanItems = Lists.newArrayList(); + } else { + toPlanItems = Lists.newArrayList(toPlanItems); + } + int i = 0; + for( Object item : ((Iterable) value) ) { + if( !(item instanceof Map) ) { + continue; + } + PlanItem entity; + if( toPlanItems.size() > i ) { + entity = toPlanItems.get(i); + } else { + entity = new PlanItem(); + toPlanItems.add(entity); + } + i = (i + 1); + @SuppressWarnings( "unchecked" ) + final Map inputMap = ((Map) item); + entity.fromMap(inputMap); + } + } + } + } + super.fromMap(values); + } + + @Nonnull + @Override + protected Map toMapOfNavigationProperties() + { + final Map values = super.toMapOfNavigationProperties(); + if( toPlanItems != null ) { + (values).put("PlanItems", toPlanItems); + } + return values; + } + + /** + * Retrieval of associated PlanItem entities (one to many). This corresponds to the OData navigation property + * PlanItems. + *

+ * If the navigation property for an entity Trip has not been resolved yet, this method will not query + * further information. Instead its Option result state will be empty. + * + * @return If the information for navigation property PlanItems is already loaded, the result will contain + * the PlanItem entities. If not, an Option with result state empty is + * returned. + */ + @Nonnull + public Option> getPlanItemsIfPresent() + { + return Option.of(toPlanItems); + } + + /** + * Overwrites the list of associated PlanItem entities for the loaded navigation property PlanItems. + *

+ * If the navigation property PlanItems of a queried Trip is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param value + * List of PlanItem entities. + */ + public void setPlanItems( @Nonnull final List value ) + { + if( toPlanItems == null ) { + toPlanItems = Lists.newArrayList(); + } + toPlanItems.clear(); + toPlanItems.addAll(value); + } + + /** + * Adds elements to the list of associated PlanItem entities. This corresponds to the OData navigation + * property PlanItems. + *

+ * If the navigation property PlanItems of a queried Trip is operated lazily, an ODataException + * can be thrown in case of an OData query error. + *

+ * Please note: Lazy loading of OData entity associations is the process of asynchronous retrieval and + * persisting of items from a navigation property. If a lazy property is requested by the application for the + * first time and it has not yet been loaded, an OData query will be run in order to load the missing information + * and its result will get cached for future invocations. + * + * @param entity + * Array of PlanItem entities. + */ + public void addPlanItems( PlanItem... entity ) + { + if( toPlanItems == null ) { + toPlanItems = Lists.newArrayList(); + } + toPlanItems.addAll(Lists.newArrayList(entity)); + } + + /** + * Function that can be applied to any entity object of this class. + *

+ * + * @return Function object prepared with the given parameters to be applied to any entity object of this class. + *

+ * To execute it use the {@code service.forEntity(entity).applyFunction(thisFunction)} API. + */ + @Nonnull + public static + com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToCollection + getInvolvedPeople() + { + final Map parameters = Collections.emptyMap(); + return new com.sap.cloud.sdk.datamodel.odatav4.core.BoundFunction.SingleToCollection( + Trip.class, + Person.class, + "Trippin.GetInvolvedPeople", + parameters); + } + + /** + * Helper class to allow for fluent creation of Trip instances. + * + */ + public final static class TripBuilder + { + + private List toPlanItems = Lists.newArrayList(); + + private Trip.TripBuilder toPlanItems( final List value ) + { + toPlanItems.addAll(value); + return this; + } + + /** + * Navigation property PlanItems for Trip to multiple PlanItem. + * + * @param value + * The PlanItems to build this Trip with. + * @return This Builder to allow for a fluent interface. + */ + @Nonnull + public Trip.TripBuilder planItems( PlanItem... value ) + { + return toPlanItems(Lists.newArrayList(value)); + } + + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/DefaultTrippinService.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/DefaultTrippinService.java new file mode 100644 index 0000000000..fe1d480252 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/DefaultTrippinService.java @@ -0,0 +1,242 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.core.BatchRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.CountRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.DeleteRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.ServiceWithNavigableEntities; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueActionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueFunctionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.UpdateRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +import lombok.Getter; + +/** + *

Details:

+ * + * + * + * + * + *
OData Service:trippin
+ * + */ +public class DefaultTrippinService implements ServiceWithNavigableEntities, TrippinService +{ + + @Nonnull + @Getter + private final String servicePath; + + /** + * Creates a service using {@link TrippinService#DEFAULT_SERVICE_PATH} to send the requests. + * + */ + public DefaultTrippinService() + { + servicePath = TrippinService.DEFAULT_SERVICE_PATH; + } + + /** + * Creates a service using the provided service path to send the requests. + *

+ * Used by the fluent {@link #withServicePath(String)} method. + * + */ + private DefaultTrippinService( @Nonnull final String servicePath ) + { + this.servicePath = servicePath; + } + + @Override + @Nonnull + public DefaultTrippinService withServicePath( @Nonnull final String servicePath ) + { + return new DefaultTrippinService(servicePath); + } + + @Override + @Nonnull + public BatchRequestBuilder batch() + { + return new BatchRequestBuilder(servicePath); + } + + @Override + @Nonnull + public GetAllRequestBuilder getAllPeople() + { + return new GetAllRequestBuilder(servicePath, Person.class, "People"); + } + + @Override + @Nonnull + public CountRequestBuilder countPeople() + { + return new CountRequestBuilder(servicePath, Person.class, "People"); + } + + @Override + @Nonnull + public GetByKeyRequestBuilder getPeopleByKey( final String userName ) + { + final Map key = new HashMap(); + key.put("UserName", userName); + return new GetByKeyRequestBuilder(servicePath, Person.class, key, "People"); + } + + @Override + @Nonnull + public CreateRequestBuilder createPeople( @Nonnull final Person person ) + { + return new CreateRequestBuilder(servicePath, person, "People"); + } + + @Override + @Nonnull + public UpdateRequestBuilder updatePeople( @Nonnull final Person person ) + { + return new UpdateRequestBuilder(servicePath, person, "People"); + } + + @Override + @Nonnull + public DeleteRequestBuilder deletePeople( @Nonnull final Person person ) + { + return new DeleteRequestBuilder(servicePath, person, "People"); + } + + @Override + @Nonnull + public GetAllRequestBuilder getAllAirlines() + { + return new GetAllRequestBuilder(servicePath, Airline.class, "Airlines"); + } + + @Override + @Nonnull + public CountRequestBuilder countAirlines() + { + return new CountRequestBuilder(servicePath, Airline.class, "Airlines"); + } + + @Override + @Nonnull + public GetByKeyRequestBuilder getAirlinesByKey( final String airlineCode ) + { + final Map key = new HashMap(); + key.put("AirlineCode", airlineCode); + return new GetByKeyRequestBuilder(servicePath, Airline.class, key, "Airlines"); + } + + @Override + @Nonnull + public CreateRequestBuilder createAirlines( @Nonnull final Airline airline ) + { + return new CreateRequestBuilder(servicePath, airline, "Airlines"); + } + + @Override + @Nonnull + public UpdateRequestBuilder updateAirlines( @Nonnull final Airline airline ) + { + return new UpdateRequestBuilder(servicePath, airline, "Airlines"); + } + + @Override + @Nonnull + public DeleteRequestBuilder deleteAirlines( @Nonnull final Airline airline ) + { + return new DeleteRequestBuilder(servicePath, airline, "Airlines"); + } + + @Override + @Nonnull + public GetAllRequestBuilder getAllAirports() + { + return new GetAllRequestBuilder(servicePath, Airport.class, "Airports"); + } + + @Override + @Nonnull + public CountRequestBuilder countAirports() + { + return new CountRequestBuilder(servicePath, Airport.class, "Airports"); + } + + @Override + @Nonnull + public GetByKeyRequestBuilder getAirportsByKey( final String icaoCode ) + { + final Map key = new HashMap(); + key.put("IcaoCode", icaoCode); + return new GetByKeyRequestBuilder(servicePath, Airport.class, key, "Airports"); + } + + @Override + @Nonnull + public CreateRequestBuilder createAirports( @Nonnull final Airport airport ) + { + return new CreateRequestBuilder(servicePath, airport, "Airports"); + } + + @Override + @Nonnull + public UpdateRequestBuilder updateAirports( @Nonnull final Airport airport ) + { + return new UpdateRequestBuilder(servicePath, airport, "Airports"); + } + + @Override + @Nonnull + public DeleteRequestBuilder deleteAirports( @Nonnull final Airport airport ) + { + return new DeleteRequestBuilder(servicePath, airport, "Airports"); + } + + @Override + @Nonnull + public SingleValueFunctionRequestBuilder getPersonWithMostFriends() + { + return new SingleValueFunctionRequestBuilder(servicePath, "GetPersonWithMostFriends", Person.class); + } + + @Override + @Nonnull + public + SingleValueFunctionRequestBuilder + getNearestAirport( @Nonnull final Double lat, @Nonnull final Double lon ) + { + final LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("lat", lat); + parameters.put("lon", lon); + return new SingleValueFunctionRequestBuilder( + servicePath, + "GetNearestAirport", + parameters, + Airport.class); + } + + @Override + @Nonnull + public SingleValueActionRequestBuilder resetDataSource() + { + return new SingleValueActionRequestBuilder(servicePath, "ResetDataSource", Void.class); + } + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/TrippinService.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/TrippinService.java new file mode 100644 index 0000000000..58a2a882d3 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/referenceservice/services/TrippinService.java @@ -0,0 +1,398 @@ +/* + * Generated by OData VDM code generator of SAP Cloud SDK in version 4.21.0 + */ + +package com.sap.cloud.sdk.datamodel.odatav4.referenceservice.services; + +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.datamodel.odatav4.core.BatchRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.CountRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.DeleteRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueActionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueFunctionRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.core.UpdateRequestBuilder; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +/** + *

Details:

+ * + * + * + * + * + *
OData Service:trippin
+ * + */ +public interface TrippinService +{ + + /** + * If no other path was provided via the {@link #withServicePath(String)} method, this is the default service path + * used to access the endpoint. + * + */ + String DEFAULT_SERVICE_PATH = "/TripPinRESTierServiceTrippin"; + + /** + * Overrides the default service path and returns a new service instance with the specified service path. Also + * adjusts the respective entity URLs. + * + * @param servicePath + * Service path that will override the default. + * @return A new service instance with the specified service path. + */ + @Nonnull + TrippinService withServicePath( @Nonnull final String servicePath ); + + /** + * Creates a batch request builder object. + * + * @return A request builder to handle batch operation on this service. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.BatchRequestBuilder#execute(Destination) execute} method + * on the request builder object. + */ + @Nonnull + BatchRequestBuilder batch(); + + /** + * Fetch multiple {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} + * entities. + * + * @return A request builder to fetch multiple + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetAllRequestBuilder getAllPeople(); + + /** + * Fetch the number of entries from the + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity collection + * matching the filter and search expressions. + * + * @return A request builder to fetch the count of + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CountRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CountRequestBuilder countPeople(); + + /** + * Fetch a single {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} + * entity using key fields. + * + * @param userName + *

+ * Constraints: Not nullable + *

+ * @return A request builder to fetch a single + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity + * using key fields. This request builder allows methods which modify the underlying query to be called + * before executing the query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetByKeyRequestBuilder getPeopleByKey( final String userName ); + + /** + * Create a new {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity + * and save it to the S/4HANA system. + * + * @param person + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity + * object that will be created in the S/4HANA system. + * @return A request builder to create a new + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity. To + * perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CreateRequestBuilder createPeople( @Nonnull final Person person ); + + /** + * Update an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} + * entity and save it to the S/4HANA system. + * + * @param person + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity + * object that will be updated in the S/4HANA system. + * @return A request builder to update an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity. To + * perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.UpdateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + UpdateRequestBuilder updatePeople( @Nonnull final Person person ); + + /** + * Deletes an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} + * entity in the S/4HANA system. + * + * @param person + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity + * object that will be deleted in the S/4HANA system. + * @return A request builder to delete an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person Person} entity. To + * perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.DeleteRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + DeleteRequestBuilder deletePeople( @Nonnull final Person person ); + + /** + * Fetch multiple {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} + * entities. + * + * @return A request builder to fetch multiple + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetAllRequestBuilder getAllAirlines(); + + /** + * Fetch the number of entries from the + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity collection + * matching the filter and search expressions. + * + * @return A request builder to fetch the count of + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CountRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CountRequestBuilder countAirlines(); + + /** + * Fetch a single {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} + * entity using key fields. + * + * @param airlineCode + *

+ * Constraints: Not nullable + *

+ * @return A request builder to fetch a single + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity + * using key fields. This request builder allows methods which modify the underlying query to be called + * before executing the query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetByKeyRequestBuilder getAirlinesByKey( final String airlineCode ); + + /** + * Create a new {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} + * entity and save it to the S/4HANA system. + * + * @param airline + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity + * object that will be created in the S/4HANA system. + * @return A request builder to create a new + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CreateRequestBuilder createAirlines( @Nonnull final Airline airline ); + + /** + * Update an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline + * Airline} entity and save it to the S/4HANA system. + * + * @param airline + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity + * object that will be updated in the S/4HANA system. + * @return A request builder to update an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.UpdateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + UpdateRequestBuilder updateAirlines( @Nonnull final Airline airline ); + + /** + * Deletes an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline + * Airline} entity in the S/4HANA system. + * + * @param airline + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity + * object that will be deleted in the S/4HANA system. + * @return A request builder to delete an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airline Airline} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.DeleteRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + DeleteRequestBuilder deleteAirlines( @Nonnull final Airline airline ); + + /** + * Fetch multiple {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} + * entities. + * + * @return A request builder to fetch multiple + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetAllRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetAllRequestBuilder getAllAirports(); + + /** + * Fetch the number of entries from the + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity collection + * matching the filter and search expressions. + * + * @return A request builder to fetch the count of + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entities. + * This request builder allows methods which modify the underlying query to be called before executing the + * query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CountRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CountRequestBuilder countAirports(); + + /** + * Fetch a single {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} + * entity using key fields. + * + * @param icaoCode + *

+ * Constraints: Not nullable + *

+ * @return A request builder to fetch a single + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity + * using key fields. This request builder allows methods which modify the underlying query to be called + * before executing the query itself. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.GetByKeyRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + GetByKeyRequestBuilder getAirportsByKey( final String icaoCode ); + + /** + * Create a new {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} + * entity and save it to the S/4HANA system. + * + * @param airport + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity + * object that will be created in the S/4HANA system. + * @return A request builder to create a new + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.CreateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + CreateRequestBuilder createAirports( @Nonnull final Airport airport ); + + /** + * Update an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport + * Airport} entity and save it to the S/4HANA system. + * + * @param airport + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity + * object that will be updated in the S/4HANA system. + * @return A request builder to update an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.UpdateRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + UpdateRequestBuilder updateAirports( @Nonnull final Airport airport ); + + /** + * Deletes an existing {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport + * Airport} entity in the S/4HANA system. + * + * @param airport + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity + * object that will be deleted in the S/4HANA system. + * @return A request builder to delete an existing + * {@link com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Airport Airport} entity. + * To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.DeleteRequestBuilder#execute + * execute} method on the request builder object. + */ + @Nonnull + DeleteRequestBuilder deleteAirports( @Nonnull final Airport airport ); + + /** + *

+ * Creates a request builder for the GetPersonWithMostFriends OData function. + *

+ * + * @return A request builder object that will execute the GetPersonWithMostFriends OData function with the + * provided parameters. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueFunctionRequestBuilder#execute execute} method + * on the request builder object. + */ + @Nonnull + SingleValueFunctionRequestBuilder getPersonWithMostFriends(); + + /** + *

+ * Creates a request builder for the GetNearestAirport OData function. + *

+ * + * @param lon + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: lon + *

+ * @param lat + * Constraints: Not nullable + *

+ * Original parameter name from the Odata EDM: lat + *

+ * @return A request builder object that will execute the GetNearestAirport OData function with the provided + * parameters. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueFunctionRequestBuilder#execute execute} method + * on the request builder object. + */ + @Nonnull + SingleValueFunctionRequestBuilder + getNearestAirport( @Nonnull final Double lat, @Nonnull final Double lon ); + + /** + *

+ * Creates a request builder for the ResetDataSource OData action. + *

+ * + * @return A request builder object that will execute the ResetDataSource OData action with the provided + * parameters. To perform execution, call the + * {@link com.sap.cloud.sdk.datamodel.odatav4.core.SingleValueActionRequestBuilder#execute execute} method + * on the request builder object. + */ + @Nonnull + SingleValueActionRequestBuilder resetDataSource(); + +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CollectionValueActionRequestBuilderTest/ActionNoParametersRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CollectionValueActionRequestBuilderTest/ActionNoParametersRequestBody.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CollectionValueActionRequestBuilderTest/ActionNoParametersRequestBody.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CsrfTokenHandlingTest/CreateResponse.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CsrfTokenHandlingTest/CreateResponse.json new file mode 100644 index 0000000000..6993dec68c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/CsrfTokenHandlingTest/CreateResponse.json @@ -0,0 +1,4 @@ +{ + "@odata.context": "/sap/ODATA_SRV$metadata#People/$entity", + "ShoeSize": 46 +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetAllResponseBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetAllResponseBody.json new file mode 100644 index 0000000000..379421658b --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetAllResponseBody.json @@ -0,0 +1,20 @@ +{ + "value": [ + { + "@context": "context", + "@type": "type", + "@etag": "W/\"123\"", + "UserName": "russellwhyte", + "FirstName": "Russell", + "LastName": "Whyte" + }, + { + "@context": "context", + "@type": "type", + "@etag": "W/\"999\"", + "UserName": "scottketchum", + "FirstName": "Scott", + "LastName": "Ketchum" + } + ] +} \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetSingleResponseBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetSingleResponseBody.json new file mode 100644 index 0000000000..552beb42bf --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ETagTest/GetSingleResponseBody.json @@ -0,0 +1,5 @@ +{ + "UserName": "russellwhyte", + "FirstName": "Russell", + "LastName": "Whyte" +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/HeadersHandlingTest/CreateResponse.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/HeadersHandlingTest/CreateResponse.json new file mode 100644 index 0000000000..6993dec68c --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/HeadersHandlingTest/CreateResponse.json @@ -0,0 +1,4 @@ +{ + "@odata.context": "/sap/ODATA_SRV$metadata#People/$entity", + "ShoeSize": 46 +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyRequest.txt new file mode 100644 index 0000000000..87e43fd868 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyRequest.txt @@ -0,0 +1 @@ +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyResponse.txt new file mode 100644 index 0000000000..906fce312f --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchEmptyResponse.txt @@ -0,0 +1,2 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsErrorResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsErrorResponse.txt new file mode 100644 index 0000000000..a4b70ad11a --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsErrorResponse.txt @@ -0,0 +1,21 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"angelhuffman","FirstName":"Angel","LastName":"Huffman"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 +dataserviceversion: 1.0 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingRequest.txt new file mode 100644 index 0000000000..65acbfb138 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingRequest.txt @@ -0,0 +1,28 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET People(%27one%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +GET People(%27two%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET People(%27three%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingResponse.txt new file mode 100644 index 0000000000..14db6511d8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsMissingResponse.txt @@ -0,0 +1,20 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"one"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 404 Not Found +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"error":{"code":"","message":"The request resource is not found."}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsRequest.txt new file mode 100644 index 0000000000..075656d033 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsRequest.txt @@ -0,0 +1,19 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET People?$top=1 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +GET People?$top=2&$skip=1 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsResponse.txt new file mode 100644 index 0000000000..04c690beb0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchOnlyReadsResponse.txt @@ -0,0 +1,21 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"angelhuffman","FirstName":"Angel","LastName":"Huffman"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"klauskinski","FirstName":"Klaus","LastName":"Kinski"},{"UserName":"DanielBruehl","FirstName":"Daniel","LastName":"Brühl"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorRequest.txt new file mode 100644 index 0000000000..929ca524f6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorRequest.txt @@ -0,0 +1,37 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"UserName":"JohnDoe1", "FirstName":"John"} + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"UserName":"JohnDoe2"} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET People(%27klauskinski%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponse.txt new file mode 100644 index 0000000000..7486fe8290 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponse.txt @@ -0,0 +1,23 @@ +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa +Content-Type: multipart/mixed; boundary=changesetresponse_4459f831-1259-4f7c-89a0-95162d172489 + +--changesetresponse_4459f831-1259-4f7c-89a0-95162d172489 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 400 Bad Request +Content-Type: application/json; charset=utf-8 + +{"error": {"code": "005056A509B11EE1B9A8FEC11C23378E","message": {"lang": "en","value": "The FirstName field is required"}}} +--changesetresponse_4459f831-1259-4f7c-89a0-95162d172489-- +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"klauskinski","FirstName":"Klaus","LastName":"Kinski","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponseWithoutChangeset.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponseWithoutChangeset.txt new file mode 100644 index 0000000000..95a0c1abde --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesErrorResponseWithoutChangeset.txt @@ -0,0 +1,19 @@ +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 400 Bad Request +Content-Type: application/json; charset=utf-8 + +[{"Id":"System.ComponentModel.DataAnnotations.RequiredAttribute","Message":"The FirstName field is required.","PropertyName":"FirstName","Severity":"Error"}] +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"klauskinski","FirstName":"Klaus","LastName":"Kinski","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--batchresponse_2e5e59e3-ef58-493c-b947-638f445896aa-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessRequest.txt new file mode 100644 index 0000000000..c1517843ad --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessRequest.txt @@ -0,0 +1,37 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"UserName":"JohnDoe1", "FirstName":"John"} + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"UserName":"JohnDoe2", "FirstName":"John"} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET People(%27foo%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessResponse.txt new file mode 100644 index 0000000000..c69262bcd8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataClientBatchResponseParsingUnitTest/BatchReadsAndWritesSuccessResponse.txt @@ -0,0 +1,37 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 201 Created +Location: https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/People('menow2') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"JohnDoe1","FirstName":"John","LastName":"","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +HTTP/1.1 201 Created +Location: https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/People('menow3') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"JohnDoe2","FirstName":"John","LastName":"","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 404 Not Found +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"error":{"code":"","message":"The request resource is not found."}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyRequest.txt new file mode 100644 index 0000000000..87e43fd868 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyRequest.txt @@ -0,0 +1 @@ +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyResponse.txt new file mode 100644 index 0000000000..906fce312f --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchEmptyResponse.txt @@ -0,0 +1,2 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsErrorResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsErrorResponse.txt new file mode 100644 index 0000000000..338622ee98 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsErrorResponse.txt @@ -0,0 +1,20 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"angelhuffman","FirstName":"Angel","LastName":"Huffman"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsRequest.txt new file mode 100644 index 0000000000..075656d033 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsRequest.txt @@ -0,0 +1,19 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET People?$top=1 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +GET People?$top=2&$skip=1 HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsResponse.txt new file mode 100644 index 0000000000..04c690beb0 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchOnlyReadsResponse.txt @@ -0,0 +1,21 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"angelhuffman","FirstName":"Angel","LastName":"Huffman"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People","value":[{"UserName":"klauskinski","FirstName":"Klaus","LastName":"Kinski"},{"UserName":"DanielBruehl","FirstName":"Daniel","LastName":"Brühl"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessRequest.txt new file mode 100644 index 0000000000..b568462c2e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessRequest.txt @@ -0,0 +1,37 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"@odata.type":"#Trippin.Person","UserName":"JohnDoe1","FirstName":"John","Friends":[],"Trips":[]} + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +POST People HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"@odata.type":"#Trippin.Person","UserName":"JohnDoe2","FirstName":"John","Friends":[],"Trips":[]} + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +GET People(%27foo%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessResponse.txt new file mode 100644 index 0000000000..c69262bcd8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchReferenceServiceUnitTest/BatchReadsAndWritesSuccessResponse.txt @@ -0,0 +1,37 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 201 Created +Location: https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/People('menow2') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"JohnDoe1","FirstName":"John","LastName":"","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +HTTP/1.1 201 Created +Location: https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/People('menow3') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(w3zgpoiit3rigb4hkixmletd))/$metadata#People/$entity","UserName":"JohnDoe2","FirstName":"John","LastName":"","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":null,"Features":[],"AddressInfo":[],"HomeAddress":null} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 404 Not Found +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"error":{"code":"","message":"The request resource is not found."}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- + diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessRequest.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessRequest.txt new file mode 100644 index 0000000000..9f0b46136d --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessRequest.txt @@ -0,0 +1,100 @@ +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +GET EntityCollection HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +GET EntityCollection(%27foobar%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +POST EntityCollection HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"@odata.type":"#TestEntity"} + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 4 + +PATCH EntityCollection(%27upd%27) HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"@odata.type":"#TestEntity"} + +--changeset_00000000-0000-0000-0000-000000000002 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 5 + +DELETE EntityCollection(%27del%27) HTTP/1.1 +Accept: application/json + + +--changeset_00000000-0000-0000-0000-000000000002-- + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 6 + +GET function-single(secret=%27pass%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 7 + +GET function-multiple(secret=%27pass%27) HTTP/1.1 +Accept: application/json + + +--batch_00000000-0000-0000-0000-000000000001 +Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 8 + +POST action-single HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"secret":"pass"} + +--changeset_00000000-0000-0000-0000-000000000003 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 9 + +POST action-multiple HTTP/1.1 +Accept: application/json +Content-Type: application/json + +{"secret":"pass"} + +--changeset_00000000-0000-0000-0000-000000000003-- + +--batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessResponse.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessResponse.txt new file mode 100644 index 0000000000..7290820ed7 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODataV4BatchRequestUnitTest/BatchReadsAndWritesSuccessResponse.txt @@ -0,0 +1,87 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","value":[{"id":"adam"},{"id":"eve"},{"id":"foobar"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity/$entity","id":"foobar"} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","id":"new"} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","id":"updated"} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +HTTP/1.1 204 No Content + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: text/plain + +42 +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"value":["Something","here"]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"value":42} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json + +{"value":["Something","here"]} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithChangeset.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithChangeset.txt new file mode 100644 index 0000000000..bc821ac212 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithChangeset.txt @@ -0,0 +1,51 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","value":[{"id":"adam"},{"id":"eve"},{"id":"foobar"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity/$entity","id":"foobar"} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 1 + +HTTP/1.1 201 Created +Location: https://localhost/service/TestEntities('new') +Content-Type: application/json; odata.metadata=minimal +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","id":"new"} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 2 + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","id":"updated"} +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 +Content-Type: application/http +Content-Transfer-Encoding: binary +Content-ID: 3 + +HTTP/1.1 204 No Content + +--changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithError.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithError.txt new file mode 100644 index 0000000000..f2d88779c4 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithError.txt @@ -0,0 +1,28 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","value":[{"id":"adam"},{"id":"eve"},{"id":"foobar"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity/$entity","id":"foobar"} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 400 Bad Request +Content-Type: application/json;charset=utf-8 +Content-Length: 1050 + +{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithoutChangeset.txt b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithoutChangeset.txt new file mode 100644 index 0000000000..359e8efa0e --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/ODatav4BatchConnectionTest/BatchResponseWithoutChangeset.txt @@ -0,0 +1,19 @@ +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity","value":[{"id":"adam"},{"id":"eve"},{"id":"foobar"}]} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef +Content-Type: application/http +Content-Transfer-Encoding: binary + +HTTP/1.1 200 OK +Content-Type: application/json; odata.metadata=minimal; odata.streaming=true +OData-Version: 4.0 + +{"@odata.context":"https://localhost/service/$metadata#TestEntity/$entity","id":"foobar"} +--batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionComplexTypeRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionComplexTypeRequestBody.json new file mode 100644 index 0000000000..58613d4755 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionComplexTypeRequestBody.json @@ -0,0 +1,6 @@ +{ + "complexEntity": {"@odata.type": "#com.sap.cloud.sdk.ComplexType", + "City": "Stockholm", + "Country": "Sweden" + } +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionEntityRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionEntityRequestBody.json new file mode 100644 index 0000000000..c2ec3886d8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionEntityRequestBody.json @@ -0,0 +1,4 @@ +{ + "entityParameter": {"@odata.type": "#com.sap.cloud.sdk.TestEntity", + "Name": "Tester"} +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNoParametersRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNoParametersRequestBody.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNoParametersRequestBody.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNullEntityRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNullEntityRequestBody.json new file mode 100644 index 0000000000..1714b4861f --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionNullEntityRequestBody.json @@ -0,0 +1,3 @@ +{ + "entityParameter": null +} diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionParametersRequestBody.json b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionParametersRequestBody.json new file mode 100644 index 0000000000..e0c51b0612 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/SingleValueActionRequestBuilderTest/ActionParametersRequestBody.json @@ -0,0 +1,9 @@ +{ + "stringParameter" : "test", + "booleanParameter" : true, + "integerParameter" : 9000, + "decimalParameter" : 3.14, + "durationParameter" : "PT8H", + "dateTimeOffsetParameter":"2020-03-12T05:02:30Z", + "timeOfDayParameter":"13:03:39.999" +} \ No newline at end of file diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/unused_trippin.edmx b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/unused_trippin.edmx new file mode 100644 index 0000000000..26ed9a8945 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/resources/unused_trippin.edmx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + + + + + + \ No newline at end of file From 9e5ae3f4c41d06883dbdc661ff2cbf8a814e2819 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Tue, 8 Sep 2026 14:42:09 -0300 Subject: [PATCH 3/9] update datamodel/pom.xml --- datamodel/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/datamodel/pom.xml b/datamodel/pom.xml index 430a4cd4a8..9018e060aa 100644 --- a/datamodel/pom.xml +++ b/datamodel/pom.xml @@ -37,6 +37,7 @@ odata odata-core-apache-httpclient5 odata-v4 + odata-v4-core-apache-httpclient5 soap openapi From 268e4e9741c3b71cb4eadf460fe4e94cdd166cc4 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Tue, 8 Sep 2026 15:00:00 -0300 Subject: [PATCH 4/9] update javadocs --- .../sdk/datamodel/odata/helper/FluentHelperBasic.java | 8 +++----- release_notes.md | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java index 46b5f40bc9..db1bffed97 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.java @@ -43,8 +43,7 @@ public abstract class FluentHelperBasic impleme protected String entityCollection = null; /** - * A map containing the headers to be used for all explicit and implicit requests that are part of this FluentHelper - * implementation (csrf). + * A map containing the headers to be used for all requests that are part of this FluentHelper implementation. */ private final Map headers = new LinkedHashMap<>(); @@ -96,9 +95,8 @@ protected FluentHelperT getThis() /** * Get all headers for explicit and implicit requests. * - * @return a map containing the headers for actual request and implicit requests, e.g. csrf token request. A map - * containing the headers to be used for all implicit requests that are part of this FluentHelper - * implementation (csrf, ...). + * @return a map containing the headers to be used for all requests that are part of this FluentHelper + * implementation. */ protected Map getHeaders() { diff --git a/release_notes.md b/release_notes.md index 3112514a9d..ed6620e985 100644 --- a/release_notes.md +++ b/release_notes.md @@ -12,7 +12,7 @@ ### ✨ New Functionality -- +- Introduced the `odata-core-apache-httpclient5` and `odata-v4-core-apache-httpclient5` modules, which run on top of Apache HttpClient 5. These are drop-in replacements for `odata-core` and `odata-v4-core` (same Java packages) for consumers moving off the end-of-life Apache HttpClient 4.x stack. ### 📈 Improvements From 7c3d0351b7ffdf967dd5864dea298cba64c47a3f Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Thu, 10 Sep 2026 15:14:51 -0300 Subject: [PATCH 5/9] restore backward-compatible CSRF opt-out in HttpClient5 --- .../ApacheHttpClient5Accessor.java | 11 ++ .../connectivity/CsrfTokenInterceptor.java | 6 + .../CsrfTokenInterceptorTest.java | 27 ++++ .../odata/helper/FluentHelperByKey.java | 15 +++ .../odata/helper/FluentHelperCount.java | 15 +++ .../odata/helper/FluentHelperFunction.java | 27 ++++ .../helper/FluentHelperModification.java | 15 +++ .../odata/helper/FluentHelperRead.java | 15 +++ .../helper/batch/BatchFluentHelperBasic.java | 18 +++ .../odata/helper/ODataHeaderTest.java | 26 ++++ .../batch/ODataV2BatchRequestUnitTest.java | 24 ++++ .../odatav4/core/BatchRequestBuilder.java | 8 ++ .../odatav4/core/CountRequestBuilder.java | 15 +++ .../odatav4/core/CreateRequestBuilder.java | 14 ++ .../odatav4/core/DeleteRequestBuilder.java | 8 ++ .../odatav4/core/FunctionRequestBuilder.java | 15 +++ .../odatav4/core/GetAllRequestBuilder.java | 16 +++ .../odatav4/core/GetByKeyRequestBuilder.java | 16 +++ .../core/ModificationRequestBuilder.java | 16 +++ .../odatav4/core/ReadRequestBuilder.java | 16 +++ .../odatav4/core/UpdateRequestBuilder.java | 8 ++ .../odatav4/core/CsrfTokenOptOutTest.java | 126 ++++++++++++++++++ 22 files changed, 457 insertions(+) create mode 100644 datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java diff --git a/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5Accessor.java b/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5Accessor.java index bd2933e742..074c66795c 100644 --- a/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5Accessor.java +++ b/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/ApacheHttpClient5Accessor.java @@ -21,6 +21,17 @@ @NoArgsConstructor( access = AccessLevel.PRIVATE ) public final class ApacheHttpClient5Accessor { + /** + * Internal request-header marker that instructs the CSRF token interceptor to skip fetching a CSRF token for the + * request it is attached to. The interceptor strips this header before the request is sent, so it never reaches the + * target system. + *

+ * This is an implementation detail used by the OData VDM layer to preserve the legacy {@code withoutCsrfToken()} + * opt-out behavior and is not intended for direct use by applications. + */ + @Nonnull + public static final String SKIP_CSRF_TOKEN_HEADER = "x-sap-sdk-skip-csrf-token"; + /** * Configures the {@code HttpClient5Cache} that is used by the {@code #getHttpClient(String)} and * {@code #getHttpClient(Destination)} methods. diff --git a/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptor.java b/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptor.java index 6a110b3e50..8f6387435f 100644 --- a/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptor.java +++ b/cloudplatform/connectivity-apache-httpclient5/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptor.java @@ -39,6 +39,12 @@ class CsrfTokenInterceptor implements HttpRequestInterceptor throws HttpException, IOException { + if( request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER) ) { + request.removeHeaders(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER); + log.debug("CSRF token retrieval explicitly disabled for this request, skipping."); + return; + } + if( !MUTATING_METHODS.contains(request.getMethod().toUpperCase()) ) { return; } diff --git a/cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptorTest.java b/cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptorTest.java index 5d21b64548..13e2d45e21 100644 --- a/cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptorTest.java +++ b/cloudplatform/connectivity-apache-httpclient5/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/CsrfTokenInterceptorTest.java @@ -119,6 +119,33 @@ void tokenIsNotFetchedWhenAlreadyPresent() .isEqualTo("existing-token"); } + @Test + @SneakyThrows + void tokenIsNotFetchedAndMarkerIsStrippedWhenSkipHeaderPresent() + { + final HttpPost request = new HttpPost(REQUEST_PATH); + request.addHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + + sut.process(request, null, null); + + verify(mockHttpClient, never()).execute(any(), ArgumentMatchers.> any()); + assertThat(request.getFirstHeader(CsrfTokenInterceptor.X_CSRF_TOKEN_HEADER_KEY)).isNull(); + assertThat(request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER)).isFalse(); + } + + @Test + @SneakyThrows + void skipMarkerIsStrippedEvenOnGetRequest() + { + final HttpGet request = new HttpGet(REQUEST_PATH); + request.addHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + + sut.process(request, null, null); + + verify(mockHttpClient, never()).execute(any(), ArgumentMatchers.> any()); + assertThat(request.containsHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER)).isFalse(); + } + @Test @SneakyThrows void requestProceedsWithoutTokenWhenServerReturnsNoHeader( final WireMockRuntimeInfo wm ) diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java index c23a2b6116..41792307ed 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.java @@ -110,4 +110,19 @@ public EntityT executeRequest( @Nonnull final Destination destination ) result.attachToService(getServicePath(), destination); return result; } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same fluent helper that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Nonnull + public FluentHelperT withCsrfToken() + { + return getThis(); + } } diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java index 3a2e9a79ea..8db826f924 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.java @@ -68,4 +68,19 @@ public ODataRequestCount toRequest() { return request; } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same fluent helper that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Nonnull + public FluentHelperCount withCsrfToken() + { + return this; + } } diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java index b9accd0332..a5df8cd654 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.java @@ -213,4 +213,31 @@ protected JsonElement refineJsonResponse( @Nullable final JsonElement jsonElemen { return jsonElement; } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same fluent helper that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility. It is scheduled for removal. + */ + @Deprecated + @Nonnull + public FluentHelperT withCsrfToken() + { + return getThis(); + } + + /** + * Deactivates the CSRF token retrieval for this OData request. This is useful if the server does not support or + * require CSRF tokens as part of the request. + * + * @return The same builder + */ + @Nonnull + public FluentHelperT withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return getThis(); + } } diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java index fb7d7f17d8..f580821b4e 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.java @@ -3,6 +3,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; + /** * Representation of an OData modification request (Create, Update, Delete) as a fluent interface for further * configuring the request and {@link #executeRequest(Destination) executing} it. @@ -28,4 +30,17 @@ public FluentHelperModification( @Nonnull final String servicePath, @Nullable fi { super(servicePath, entityCollection); } + + /** + * Deactivates the CSRF token retrieval for this OData request. This is useful if the server does not support or + * require CSRF tokens as part of the request. + * + * @return The same builder + */ + @Nonnull + public FluentHelperT withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return getThis(); + } } diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java index 25088a673b..3a7b394889 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.java @@ -280,4 +280,19 @@ public FluentHelperT withPreferredPageSize( final int size ) { return withHeader("Prefer", "odata.maxpagesize=" + size); } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same fluent helper that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Nonnull + public FluentHelperT withCsrfToken() + { + return getThis(); + } } diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java index 7668ea7640..268b6f5aad 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java @@ -54,6 +54,8 @@ public abstract class BatchFluentHelperBasic uuidProvider = UUID::randomUUID; + private boolean skipCsrfTokenRetrieval = false; + /** * Get the OData service endpoint path for the current OData batch request. Usually it can be found as static member * DEFAULT_SERVICE_PATH in the service class. @@ -98,9 +100,25 @@ public ODataRequestBatch toRequest() for( final BatchRequestOperation part : requestParts ) { part.addToRequestBuilder(requestBatch); } + if( skipCsrfTokenRetrieval ) { + requestBatch.addHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + } return requestBatch; } + /** + * Deactivates the CSRF token retrieval for this OData request. This is useful if the server does not support or + * require CSRF tokens as part of the request. + * + * @return The same builder + */ + @Nonnull + public FluentHelperBatchT withoutCsrfToken() + { + skipCsrfTokenRetrieval = true; + return getThis(); + } + /** * Method handler to register a finished changeset definition. * diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java index 07e1810684..5369da9865 100644 --- a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.java @@ -23,6 +23,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.github.tomakehurst.wiremock.matching.UrlPathPattern; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; @WireMockTest @@ -142,6 +143,31 @@ void testUpdateCustomHeader() // fluent helpers + // Test that withoutCsrfToken() disables the CSRF HEAD probe and sends neither a + // CSRF token nor the internal skip marker on the actual write request. + @Test + void testUpdateWithoutCsrfTokenSkipsHead() + { + new TestEntityUpdateFluentHelper(entity).withoutCsrfToken().executeRequest(destination); + + verify(0, headRequestedFor(CSRF)); + verify( + patchRequestedFor(UPDATE) + .withoutHeader("x-csrf-token") + .withoutHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER)); + } + + // Test that the deprecated withCsrfToken() is a no-op on a read: no CSRF HEAD probe is fired. + @SuppressWarnings( "deprecation" ) + @Test + void testReadWithCsrfTokenIsNoOp() + { + new TestEntityReadFluentHelper().withCsrfToken().executeRequest(destination); + + verify(0, headRequestedFor(CSRF)); + verify(getRequestedFor(GET_ALL).withoutHeader("x-csrf-token")); + } + private static class TestEntityDeleteFluentHelper extends FluentHelperDelete diff --git a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java index 471376142d..e7fa59de1c 100644 --- a/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java +++ b/datamodel/odata-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.java @@ -2,6 +2,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.equalToIgnoreCase; import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; @@ -300,6 +301,29 @@ private static String readResourceFileCrlf( final String resourceFileName ) return result.replaceAll("(? search( @Nonnull final SearchExpression expr delegateQuery.search(expression); return this; } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same request builder that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Nonnull + public CountRequestBuilder withCsrfToken() + { + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java index d33a67c311..de043544c7 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/CreateRequestBuilder.java @@ -138,4 +138,18 @@ public ODataRequestCreate toRequest() throw new ODataSerializationException(request, entity, msg, e); } } + + /** + * Deactivates the CSRF token retrieval for this OData request. This is useful if the server does not support or + * require CSRF tokens as part of the request. + * + * @return The same builder + */ + @Override + @Nonnull + public CreateRequestBuilder withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java index 68042d6100..94a45c6582 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/DeleteRequestBuilder.java @@ -160,4 +160,12 @@ protected Class getEntityClass() { return (Class) entity.getClass(); } + + @Nonnull + @Override + public DeleteRequestBuilder withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java index 8de67f8df6..4ae0c1bb84 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/FunctionRequestBuilder.java @@ -56,4 +56,19 @@ public ODataRequestFunction toRequest() return super.toRequest(request); } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same request builder that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility. It is scheduled for removal. + */ + @Deprecated + @Override + @Nonnull + public BuilderT withCsrfToken() + { + return getThis(); + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java index d02c3b46b9..0ed7f78bc6 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetAllRequestBuilder.java @@ -255,4 +255,20 @@ private Iterable> executeInternal( @Nonnull final Destination dest final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); return toRequest().execute(httpClient).iteratePages(getEntityClass()); } + + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same request builder that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Override + @Nonnull + public GetAllRequestBuilder withCsrfToken() + { + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java index 7737030658..a0e374d0b6 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/GetByKeyRequestBuilder.java @@ -153,4 +153,20 @@ public final GetByKeyRequestBuilder select( @Nonnull final Property withCsrfToken() + { + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java index 9dd5959545..d626128d1b 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ModificationRequestBuilder.java @@ -1,5 +1,9 @@ package com.sap.cloud.sdk.datamodel.odatav4.core; +import javax.annotation.Nonnull; + +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; + /** * Interface to mark OData request types as modifying operation. * @@ -13,4 +17,16 @@ */ public interface ModificationRequestBuilder extends RequestBuilder { + /** + * Deactivates the CSRF token retrieval for this OData request. This is useful if the server does not support or + * require CSRF tokens as part of the request. + * + * @return The same builder + */ + @Nonnull + default ModificationRequestBuilder withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java index b07c804a72..8276f9b9b5 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/ReadRequestBuilder.java @@ -1,5 +1,7 @@ package com.sap.cloud.sdk.datamodel.odatav4.core; +import javax.annotation.Nonnull; + /** * Interface to mark OData request types as reading operation. * @@ -12,4 +14,18 @@ */ public interface ReadRequestBuilder extends RequestBuilder { + /** + * Activates CSRF token retrieval for this OData request. + * + * @return The same request builder that will now fetch a CSRF token. + * @deprecated CSRF token handling is now performed automatically by the underlying HTTP client. This method is a + * no-op retained only for source compatibility, as read requests never require a CSRF token. It is + * scheduled for removal. + */ + @Deprecated + @Nonnull + default ReadRequestBuilder withCsrfToken() + { + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java index 815565abf9..8d165ef244 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odatav4/core/UpdateRequestBuilder.java @@ -285,4 +285,12 @@ public UpdateRequestBuilder matchAnyVersionIdentifier() eTagSubmissionStrategy = ETagSubmissionStrategy.SUBMIT_ANY_MATCH_ETAG; return this; } + + @Override + @Nonnull + public UpdateRequestBuilder withoutCsrfToken() + { + withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, "true"); + return this; + } } diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java new file mode 100644 index 0000000000..d446e42ef8 --- /dev/null +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java @@ -0,0 +1,126 @@ +package com.sap.cloud.sdk.datamodel.odatav4.core; + +import static com.github.tomakehurst.wiremock.client.WireMock.absent; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.datamodel.odatav4.referenceservice.namespaces.trippin.Person; + +/** + * Verifies the backward-compatible CSRF opt-out ({@code withoutCsrfToken()}) and the deprecated no-op + * ({@code withCsrfToken()}) methods on the OData v4 request builders. + */ +@WireMockTest +@SuppressWarnings( "deprecation" ) +class CsrfTokenOptOutTest +{ + private static final String SERVICE_PATH = "/remoteService"; + private static final String ENTITY_URL = SERVICE_PATH + "/People('usr')"; + private static final String PEOPLE_URL = SERVICE_PATH + "/People"; + + private static final String X_CSRF_TOKEN_HEADER_KEY = "x-csrf-token"; + + private DefaultHttpDestination destination; + + @BeforeEach + void setup( @Nonnull final WireMockRuntimeInfo wm ) + { + destination = DefaultHttpDestination.builder(wm.getHttpBaseUrl()).build(); + } + + @Test + void withoutCsrfTokenSkipsHeadProbeAndSendsNoToken() + { + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, "should-not-be-used"))); + stubFor(patch(urlEqualTo(ENTITY_URL)).willReturn(ok())); + + final Person person = new Person(); + person.setUserName("usr"); + + new UpdateRequestBuilder<>(SERVICE_PATH, person, "People").withoutCsrfToken().execute(destination); + + // no CSRF HEAD probe was fired + verify(0, headRequestedFor(anyUrl())); + // the actual write carries neither a CSRF token nor the internal skip marker + verify( + patchRequestedFor(urlEqualTo(ENTITY_URL)) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, absent()) + .withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, absent())); + } + + @Test + void createWithoutCsrfTokenSkipsHeadProbe() + { + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, "should-not-be-used"))); + stubFor(post(urlPathEqualTo(PEOPLE_URL)).willReturn(ok().withHeader("Content-Type", "application/json"))); + + final Person person = new Person(); + person.setUserName("usr"); + + new CreateRequestBuilder<>(SERVICE_PATH, person, "People").withoutCsrfToken().execute(destination); + + verify(0, headRequestedFor(anyUrl())); + verify( + postRequestedFor(urlPathEqualTo(PEOPLE_URL)) + .withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, absent())); + } + + @Test + void batchWithoutCsrfTokenSkipsHeadProbe() + { + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, "should-not-be-used"))); + stubFor(post(urlPathEqualTo(SERVICE_PATH + "/$batch")).willReturn(ok())); + + final Person person = new Person(); + person.setUserName("usr"); + + new BatchRequestBuilder(SERVICE_PATH) + .withoutCsrfToken() + .addChangeset(new CreateRequestBuilder<>(SERVICE_PATH, person, "People")) + .execute(destination); + + verify(0, headRequestedFor(anyUrl())); + verify( + postRequestedFor(urlPathEqualTo(SERVICE_PATH + "/$batch")) + .withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, absent())); + } + + @Test + void withCsrfTokenIsNoOpAndReturnsConcreteType() + { + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, "should-not-be-used"))); + + // Compile-time assertion: withCsrfToken() returns the concrete builder type. + final GetAllRequestBuilder builder = + new GetAllRequestBuilder<>(SERVICE_PATH, Person.class, "People").withCsrfToken(); + + // A read request never triggers a CSRF HEAD probe. + stubFor( + get(urlPathEqualTo(PEOPLE_URL)) + .willReturn(ok().withHeader("Content-Type", "application/json").withBody("{\"value\":[]}"))); + + builder.execute(destination); + + verify(0, headRequestedFor(anyUrl())); + } +} From 310c830304e5faedeb2ed118888c31191a129a21 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Thu, 10 Sep 2026 15:48:07 -0300 Subject: [PATCH 6/9] cover withoutCsrfToken() opt-out in odata v4 CsrfTokenOptOutTest --- .../odatav4/core/CsrfTokenOptOutTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java index d446e42ef8..3301584cf2 100644 --- a/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java +++ b/datamodel/odata-v4-core-apache-httpclient5/src/test/java/com/sap/cloud/sdk/datamodel/odatav4/core/CsrfTokenOptOutTest.java @@ -5,6 +5,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.head; import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.patch; import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; @@ -105,6 +106,24 @@ void batchWithoutCsrfTokenSkipsHeadProbe() .withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, absent())); } + @Test + void actionWithoutCsrfTokenSkipsHeadProbe() + { + final String actionUrl = SERVICE_PATH + "/TestAction"; + stubFor(head(anyUrl()).willReturn(ok().withHeader(X_CSRF_TOKEN_HEADER_KEY, "should-not-be-used"))); + stubFor(post(urlPathEqualTo(actionUrl)).willReturn(noContent())); + + new SingleValueActionRequestBuilder<>(SERVICE_PATH, "TestAction", Void.class) + .withoutCsrfToken() + .execute(destination); + + verify(0, headRequestedFor(anyUrl())); + verify( + postRequestedFor(urlPathEqualTo(actionUrl)) + .withHeader(X_CSRF_TOKEN_HEADER_KEY, absent()) + .withHeader(ApacheHttpClient5Accessor.SKIP_CSRF_TOKEN_HEADER, absent())); + } + @Test void withCsrfTokenIsNoOpAndReturnsConcreteType() { From 5feb46ad96f14c58deb15ea974fda497a6bc66cc Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Fri, 11 Sep 2026 16:07:01 -0300 Subject: [PATCH 7/9] remove accidentally committed bin/ directories --- .../odata-core-apache-httpclient5/bin/pom.xml | 126 ------------------ .../odata/adapter/ODataNumberSerializer.class | Bin 3244 -> 0 bytes ...CollectionValuedFluentHelperFunction.class | Bin 1566 -> 0 bytes .../datamodel/odata/helper/EntityField.class | Bin 12029 -> 0 bytes .../odata/helper/EntityFieldAll.class | Bin 4713 -> 0 bytes .../datamodel/odata/helper/EntityLink.class | Bin 14867 -> 0 bytes .../odata/helper/EntitySelectable.class | Bin 1820 -> 0 bytes .../odata/helper/ExpressionFluentHelper.class | Bin 4469 -> 0 bytes .../odata/helper/FluentHelperBasic.class | Bin 10440 -> 0 bytes .../odata/helper/FluentHelperByKey.class | Bin 7834 -> 0 bytes .../odata/helper/FluentHelperCount.class | Bin 2962 -> 0 bytes .../odata/helper/FluentHelperCreate.class | Bin 7121 -> 0 bytes .../odata/helper/FluentHelperDelete.class | Bin 7666 -> 0 bytes .../odata/helper/FluentHelperExecutable.class | Bin 1506 -> 0 bytes .../odata/helper/FluentHelperFactory.class | Bin 12231 -> 0 bytes .../odata/helper/FluentHelperFunction.class | Bin 11111 -> 0 bytes .../helper/FluentHelperModification.class | Bin 1803 -> 0 bytes .../odata/helper/FluentHelperRead.class | Bin 12339 -> 0 bytes .../odata/helper/FluentHelperUpdate.class | Bin 12084 -> 0 bytes .../odata/helper/ModificationResponse.class | Bin 9623 -> 0 bytes .../odata/helper/ModifyPatchStrategy.class | Bin 1457 -> 0 bytes .../odata/helper/ODataEntitySerializer.class | Bin 10098 -> 0 bytes .../datamodel/odata/helper/OneToOneLink.class | Bin 1480 -> 0 bytes .../sdk/datamodel/odata/helper/Order.class | Bin 943 -> 0 bytes .../SingleValuedFluentHelperFunction.class | Bin 1561 -> 0 bytes .../datamodel/odata/helper/VdmComplex.class | Bin 3158 -> 0 bytes .../datamodel/odata/helper/VdmEntity.class | Bin 10751 -> 0 bytes .../odata/helper/VdmEntityUtil.class | Bin 3311 -> 0 bytes .../odata/helper/VdmMediaEntity.class | Bin 6797 -> 0 bytes .../datamodel/odata/helper/VdmObject.class | Bin 12571 -> 0 bytes .../BatchChangeSetFluentHelperBasic.class | Bin 8751 -> 0 bytes .../helper/batch/BatchFluentHelperBasic.class | Bin 11623 -> 0 bytes .../helper/batch/BatchRequestChangeSet.class | Bin 3499 -> 0 bytes .../BatchRequestChangeSetOperation.class | Bin 3853 -> 0 bytes .../helper/batch/BatchRequestOperation.class | Bin 1121 -> 0 bytes .../batch/BatchRequestRead$GetAll.class | Bin 9494 -> 0 bytes .../batch/BatchRequestRead$GetByKey.class | Bin 8147 -> 0 bytes .../BatchRequestRead$GetFunctionRequest.class | Bin 8193 -> 0 bytes .../odata/helper/batch/BatchRequestRead.class | Bin 6639 -> 0 bytes .../odata/helper/batch/BatchResponse.class | Bin 3825 -> 0 bytes .../helper/batch/BatchResponseChangeSet.class | Bin 1217 -> 0 bytes .../odata/helper/batch/BatchService.class | Bin 1080 -> 0 bytes .../batch/DefaultBatchResponseChangeSet.class | Bin 6351 -> 0 bytes .../batch/DefaultBatchResponseResult.class | Bin 11146 -> 0 bytes .../batch/FluentHelperBatchChangeSet.class | Bin 1367 -> 0 bytes .../batch/FluentHelperBatchEndChangeSet.class | Bin 1111 -> 0 bytes .../batch/FluentHelperServiceBatch.class | Bin 2890 -> 0 bytes .../FluentHelperServiceBatchExecute.class | Bin 1273 -> 0 bytes .../adapter/AbstractCalendarAdapter.class | Bin 4031 -> 0 bytes .../AbstractJacksonCalendarDeserializer.class | Bin 4443 -> 0 bytes .../AbstractJacksonCalendarSerializer.class | Bin 4445 -> 0 bytes .../odata/adapter/IdentityConverter.class | Bin 4278 -> 0 bytes .../JacksonLocalDateTimeDeserializer.class | Bin 3074 -> 0 bytes .../JacksonLocalDateTimeSerializer.class | Bin 3066 -> 0 bytes .../JacksonLocalTimeDeserializer.class | Bin 3026 -> 0 bytes .../adapter/JacksonLocalTimeSerializer.class | Bin 3014 -> 0 bytes .../JacksonZonedDateTimeDeserializer.class | Bin 3074 -> 0 bytes .../JacksonZonedDateTimeSerializer.class | Bin 3066 -> 0 bytes .../odata/adapter/LocalDateTimeAdapter.class | Bin 3834 -> 0 bytes .../LocalDateTimeCalendarConverter.class | Bin 7154 -> 0 bytes .../odata/adapter/LocalTimeAdapter.class | Bin 3758 -> 0 bytes .../adapter/LocalTimeCalendarConverter.class | Bin 6074 -> 0 bytes .../odata/adapter/ODataBinaryAdapter.class | Bin 3908 -> 0 bytes .../odata/adapter/ODataBooleanAdapter.class | Bin 3056 -> 0 bytes .../adapter/ODataCustomFieldAdapter.class | Bin 3840 -> 0 bytes ...ateTimeOffsetStringCalendarConverter.class | Bin 6761 -> 0 bytes ...ODataDateTimeStringCalendarConverter.class | Bin 6091 -> 0 bytes .../datamodel/odata/adapter/ODataField.class | Bin 2331 -> 0 bytes .../ODataTimeStringCalendarConverter.class | Bin 7717 -> 0 bytes ...DataVdmEntityAdapter$ODataV2Metadata.class | Bin 6771 -> 0 bytes .../odata/adapter/ODataVdmEntityAdapter.class | Bin 7834 -> 0 bytes .../ODataVdmEntityAdapterFactory.class | Bin 2738 -> 0 bytes .../adapter/ODataVdmEntityListAdapter.class | Bin 5523 -> 0 bytes .../odata/adapter/ZonedDateTimeAdapter.class | Bin 3857 -> 0 bytes .../ZonedDateTimeCalendarConverter.class | Bin 5280 -> 0 bytes .../datamodel/odata/annotation/Key.class | Bin 310 -> 0 bytes .../NoSuchEntityFieldException.class | Bin 2151 -> 0 bytes .../ODataPayloadParsingFailedException.class | Bin 1616 -> 0 bytes .../LocalDateTimeCalendarConverterTest.class | Bin 4564 -> 0 bytes .../LocalTimeCalendarConverterTest.class | Bin 3403 -> 0 bytes .../adapter/ZonedDateTimeAdapterTest.class | Bin 2567 -> 0 bytes .../ZonedDateTimeCalendarConverterTest.class | Bin 4968 -> 0 bytes ...NavigationPropertyTest$RelatedEntity.class | Bin 7537 -> 0 bytes ...ViaNavigationPropertyTest$TestEntity.class | Bin 9357 -> 0 bytes ...avigationPropertyTest$TestEntityLink.class | Bin 5951 -> 0 bytes .../CreateViaNavigationPropertyTest.class | Bin 7945 -> 0 bytes ...erTest$LocalDateTimeBooleanConverter.class | Bin 7074 -> 0 bytes ...tomFieldTypeConverterTest$TestEntity.class | Bin 7735 -> 0 bytes .../helper/CustomFieldTypeConverterTest.class | Bin 8279 -> 0 bytes ...uentHelperEtagParsingTest$TestEntity.class | Bin 6232 -> 0 bytes .../helper/FluentHelperEtagParsingTest.class | Bin 9281 -> 0 bytes ...tSingleDeserializationTest$TestInput.class | Bin 7421 -> 0 bytes ...gleDeserializationTest$TestingEntity.class | Bin 7241 -> 0 bytes ...ctionImportSingleDeserializationTest.class | Bin 8359 -> 0 bytes ...ntityTest$SingleEntityFunctionImport.class | Bin 7887 -> 0 bytes ...ImportSingleEntityTest$TestingEntity.class | Bin 6919 -> 0 bytes .../FunctionImportSingleEntityTest.class | Bin 7346 -> 0 bytes .../helper/HttpResponseEvaluationTest.class | Bin 10450 -> 0 bytes .../ModificationResponseTest$TestObject.class | Bin 8305 -> 0 bytes .../helper/ModificationResponseTest.class | Bin 5856 -> 0 bytes .../ODataBatchRequestTest$TestBatch.class | Bin 6853 -> 0 bytes ...aBatchRequestTest$TestBatchChangeset.class | Bin 6962 -> 0 bytes .../ODataBatchRequestTest$TestEntity.class | Bin 8800 -> 0 bytes .../odata/helper/ODataBatchRequestTest.class | Bin 7895 -> 0 bytes .../odata/helper/ODataChangedFieldsTest.class | Bin 8245 -> 0 bytes .../helper/ODataEntitySerializerTest.class | Bin 8234 -> 0 bytes .../helper/ODataFilterTest$TestEntity.class | Bin 5994 -> 0 bytes .../helper/ODataFilterTest$TestInput.class | Bin 6491 -> 0 bytes .../odata/helper/ODataFilterTest.class | Bin 6260 -> 0 bytes ...derTest$TestEntityCreateFluentHelper.class | Bin 6709 -> 0 bytes ...derTest$TestEntityDeleteFluentHelper.class | Bin 6709 -> 0 bytes ...eaderTest$TestEntityReadFluentHelper.class | Bin 6585 -> 0 bytes ...derTest$TestEntityUpdateFluentHelper.class | Bin 6709 -> 0 bytes .../odata/helper/ODataHeaderTest.class | Bin 10272 -> 0 bytes ...andTest$TestEntity$TestEntityBuilder.class | Bin 8350 -> 0 bytes .../ODataSelectExpandTest$TestEntity.class | Bin 10874 -> 0 bytes ...electExpandTest$TestEntitySelectable.class | Bin 583 -> 0 bytes ...ExpandTest$TestEntitySelectableField.class | Bin 6340 -> 0 bytes ...tExpandTest$TestEntitySelectableLink.class | Bin 7551 -> 0 bytes ...est$TestEntitySelectableOneToOneLink.class | Bin 6944 -> 0 bytes .../odata/helper/ODataSelectExpandTest.class | Bin 6827 -> 0 bytes ...ionTest$TestEntity$TestEntityBuilder.class | Bin 11775 -> 0 bytes .../ODataSerializationTest$TestEntity.class | Bin 12085 -> 0 bytes .../odata/helper/ODataSerializationTest.class | Bin 6728 -> 0 bytes ...ImportBatchIntegrationTest$TestBatch.class | Bin 6142 -> 0 bytes ...chIntegrationTest$TestBatchChangeset.class | Bin 5301 -> 0 bytes ...V2FunctionImportBatchIntegrationTest.class | Bin 5723 -> 0 bytes ...ortGetBatchIntegrationTest$TestBatch.class | Bin 6182 -> 0 bytes ...unctionImportGetBatchIntegrationTest.class | Bin 5221 -> 0 bytes ...tionImportIntegrationTest$TestEntity.class | Bin 7513 -> 0 bytes ...ODataV2FunctionImportIntegrationTest.class | Bin 8897 -> 0 bytes .../helper/PaginationIntegrationTest.class | Bin 9695 -> 0 bytes ...ionUnitTest$Customer$CustomerBuilder.class | Bin 6504 -> 0 bytes .../helper/PaginationUnitTest$Customer.class | Bin 8451 -> 0 bytes .../PaginationUnitTest$CustomerField.class | Bin 5654 -> 0 bytes ...aginationUnitTest$CustomerSelectable.class | Bin 565 -> 0 bytes .../odata/helper/PaginationUnitTest.class | Bin 22714 -> 0 bytes .../helper/SerializerRaceConditionTest.class | Bin 3778 -> 0 bytes .../helper/ServicePathTest$MediaEntity.class | Bin 7417 -> 0 bytes .../ServicePathTest$RelatedEntity.class | Bin 8992 -> 0 bytes .../ServicePathTest$RelatedEntityLink.class | Bin 5927 -> 0 bytes .../helper/ServicePathTest$TestEntity.class | Bin 8810 -> 0 bytes .../ServicePathTest$TestEntityLink.class | Bin 5912 -> 0 bytes .../ServicePathTest$TransitiveEntity.class | Bin 7167 -> 0 bytes .../odata/helper/ServicePathTest.class | Bin 7872 -> 0 bytes .../SpecialCharactersTest$Ticket1.class | Bin 7677 -> 0 bytes .../SpecialCharactersTest$Ticket2.class | Bin 7877 -> 0 bytes .../odata/helper/SpecialCharactersTest.class | Bin 6932 -> 0 bytes ...TestVdmComplex$TestVdmComplexBuilder.class | Bin 8162 -> 0 bytes .../odata/helper/TestVdmComplex.class | Bin 8496 -> 0 bytes .../TestVdmEntity$TestVdmEntityBuilder.class | Bin 10628 -> 0 bytes .../odata/helper/TestVdmEntity.class | Bin 13165 -> 0 bytes ...checkedFilterExpressionTest$MyEntity.class | Bin 5131 -> 0 bytes ...kedFilterExpressionTest$MySelectable.class | Bin 3626 -> 0 bytes .../UncheckedFilterExpressionTest.class | Bin 5719 -> 0 bytes .../odata/helper/VdmComplexTest.class | Bin 5865 -> 0 bytes .../helper/VdmEntityTest$TestEntity.class | Bin 8291 -> 0 bytes .../odata/helper/VdmEntityTest.class | Bin 12915 -> 0 bytes ...tityVersionIdentifierTest$TestEntity.class | Bin 8660 -> 0 bytes .../VdmEntityVersionIdentifierTest.class | Bin 7586 -> 0 bytes .../ODataV2BatchFunctionImportTest.class | Bin 10677 -> 0 bytes ...questUnitTest$TestParameter$Executor.class | Bin 5703 -> 0 bytes ...V2BatchRequestUnitTest$TestParameter.class | Bin 6932 -> 0 bytes .../batch/ODataV2BatchRequestUnitTest.class | Bin 9320 -> 0 bytes .../batch/ODatav2BatchConnectionTest.class | Bin 9320 -> 0 bytes .../TestVdmEntityBatch$TestEntityByKey.class | Bin 6281 -> 0 bytes .../TestVdmEntityBatch$TestEntityCreate.class | Bin 5759 -> 0 bytes .../TestVdmEntityBatch$TestEntityDelete.class | Bin 5759 -> 0 bytes .../TestVdmEntityBatch$TestEntityRead.class | Bin 5837 -> 0 bytes .../TestVdmEntityBatch$TestEntityUpdate.class | Bin 5759 -> 0 bytes ...unctionImportCollectionResultHttpGet.class | Bin 6687 -> 0 bytes ...tityBatch$TestFunctionImportHttpPost.class | Bin 6619 -> 0 bytes ...ctionImportSingleEntityResultHttpGet.class | Bin 6684 -> 0 bytes ...estFunctionImportSingleResultHttpGet.class | Bin 6662 -> 0 bytes ...dmEntityBatch$TestVdmEntityChangeset.class | Bin 6382 -> 0 bytes ...mEntityBatch$TestVdmEntitySelectable.class | Bin 539 -> 0 bytes .../helper/batch/TestVdmEntityBatch.class | Bin 7789 -> 0 bytes .../adapter/LocalDateTimeAdapterTest.class | Bin 4182 -> 0 bytes .../odata/adapter/LocalTimeAdapterTest.class | Bin 4032 -> 0 bytes .../adapter/ODataBinaryAdapterTest.class | Bin 4541 -> 0 bytes .../adapter/ODataCustomFieldAdapterTest.class | Bin 10068 -> 0 bytes ...aDateTimeStringCalendarConverterTest.class | Bin 7715 -> 0 bytes ...itivesEntity$PrimitivesEntityBuilder.class | Bin 12358 -> 0 bytes ...rimitiveAdapterTest$PrimitivesEntity.class | Bin 12530 -> 0 bytes .../adapter/ODataPrimitiveAdapterTest.class | Bin 6731 -> 0 bytes .../adapter/ZonedDateTimeAdapterTest.class | Bin 5258 -> 0 bytes .../BatchRequestFunctionImportWithGet.txt | 10 -- .../BatchRequestFunctionImportWithPost.txt | 17 --- ...FunctionImportWithPostWithCustomHeader.txt | 18 --- .../BatchResponse.txt | 90 ------------- .../BatchRequest.txt | 83 ------------ .../BatchRequestWithCustomHeaders.txt | 90 ------------- .../BatchResponse.txt | 63 --------- .../BatchResponseWithChangeset.txt | 62 --------- .../BatchResponseWithError.txt | 29 ---- .../BatchResponseWithoutChangeset.txt | 19 --- 196 files changed, 607 deletions(-) delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/pom.xml delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntitySelectable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperByKey.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCreate.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperExecutable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperModification.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperRead.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponse.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ModifyPatchStrategy.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/OneToOneLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/SingleValuedFluentHelperFunction.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetAll.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetFunctionRequest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractCalendarAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarSerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/IdentityConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeSerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeDeserializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBooleanAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataTimeStringCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapterFactory.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityListAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeCalendarConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/annotation/Key.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest$LocalDateTimeBooleanConverter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CustomFieldTypeConverterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatchChangeset.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataChangedFieldsTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializerTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest$TestInput.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityDeleteFluentHelper.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityReadFluentHelper.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity$TestEntityBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableOneToOneLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest$TestBatch.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportGetBatchIntegrationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$Customer$CustomerBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$Customer.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerSelectable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SerializerRaceConditionTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$MediaEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntityLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest$Ticket1.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest$Ticket2.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/SpecialCharactersTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity$TestVdmEntityBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MySelectable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplexTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchFunctionImportTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest$TestParameter$Executor.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest$TestParameter.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODataV2BatchRequestUnitTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityByKey.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityUpdate.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportCollectionResultHttpGet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportHttpPost.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalTimeAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataBinaryAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest$PrimitivesEntity$PrimitivesEntityBuilder.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest$PrimitivesEntity.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataPrimitiveAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt delete mode 100644 datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt diff --git a/datamodel/odata-core-apache-httpclient5/bin/pom.xml b/datamodel/odata-core-apache-httpclient5/bin/pom.xml deleted file mode 100644 index 04351015c3..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - 4.0.0 - - com.sap.cloud.sdk.datamodel - datamodel-parent - 5.35.0-SNAPSHOT - - odata-core-apache-httpclient5 - jar - Data Model - OData Services - Core (HttpClient 5) - OData Services data model (VDM) - core classes using Apache HttpClient 5. - https://sap.github.io/cloud-sdk/docs/java/getting-started - - SAP SE - https://www.sap.com - - - - The Apache Software License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - - - - - SAP - cloudsdk@sap.com - SAP SE - https://www.sap.com - - - - false - - - - com.sap.cloud.sdk.datamodel - odata-client-apache-httpclient5 - ${project.version} - - - com.sap.cloud.sdk.cloudplatform - cloudplatform-core - - - com.sap.cloud.sdk.cloudplatform - cloudplatform-connectivity - - - com.sap.cloud.sdk.cloudplatform - connectivity-apache-httpclient5 - - - com.sap.cloud.sdk.datamodel - fluent-result - - - org.slf4j - slf4j-api - - - com.google.guava - guava - - - com.google.code.gson - gson - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - org.apache.httpcomponents.core5 - httpcore5 - - - org.apache.httpcomponents.client5 - httpclient5 - - - io.vavr - vavr - - - - org.projectlombok - lombok - provided - - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - org.wiremock - wiremock - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-params - test - - - diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/adapter/ODataNumberSerializer.class deleted file mode 100644 index 0a0caba4121f7c1fb225c6b2212949950817ae32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3244 zcmeHJ-EY${5I>Kyu4QbnF}`7D4CnwQFGxrfA@MOpQN|D*lX&zJuQeogu$^k-A438O z-g)K!L7bD7Wl(652I7H-IQ}@_o$u~&7Ol^-$75*6{-S8nRFh^3ZmfAs;bo@B`e*KMu^i^UtRt56|uBcq7{4u~uy+U?Xe_N0sN6?}Rj3h|-lbjETImTmM8@ZzOU;dndpx43P%2_(g_1PY zY9r=}xkc->wJxW2JLS|d@SHHO5Jp2LrLuH`(>@cAwk#Ef3KAxhZP zD#7*Z49_C!g)uXRZi<+jzbPf+DA*~GvhWq_aJqmm_s|un5W2!cS9srC;pMLIc3iPQ zBgGAMk4~5kyVRm>_*RN9E%QT1d(P2bZn4q17gi8SsV6=PZkDncAZ(nX&E! zsVI}qhXvyj%X;mxQF0e?1MESqqAUkTy;cBp2+?YfYb~Ndw%x-^Ntwm*5N1xjWke~L z__!aS`1|}udcl*CR9kcTVn1{$yC6?Z_m#AKYuJNZNYrr=@=LR{yAEi-rf~{9)glpA z^oBj@p6EQ_p-9*$p4)e(e?od4CgAWS9E2$XbB$oPgtV;|veR@WW;#F+6|u9%uxb#P z2~^19b%wPH*E^dP+ZBeuYOt?mut}h1`bCkz%|=jY(hOV(HiMtVPje|h+JO&pIrA)& zRwTS7d%_4*`>v~PdB~yG^`MVjCotFn0nQh{zxI@AmT)ZFrTY_bnv8uOq15(;42j9n zF!(<`>}gHP`S5>w_^xj&8|ODN9s;ASG9B`V*u)VyJ+M>FyC6hOTXZC|nMT6vrA$&3 z?!8Fm`gR}3cMxQAE$QmAFcwSQBf&M(VRyR<7YH0GMR^r25;*xY#8=@GfvNJTYof1U z$6RELDlB2U?6L-d$rWJ?<~O%8Z~?HJ#zr1s3}**n9LFlIZs0D#fBij7yqKxKg~^XO z%`}dV2JkzA<77bvM`0%4n}uU|a}MWqhTk7Ui)os)GB4s8a((%?=P% gfirk#49>#2{C^(i@egnnzbRCa!U8P9WoW>GuZQa;&j0`b diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/CollectionValuedFluentHelperFunction.class deleted file mode 100644 index 142e7887e6de3c1c99d35c19fe73f9f767e12f67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1566 zcmchX-%k`V5XYy-$#Lr`h=RY9LVS>y-FJ?V7{W#3i3U~@V|?iC4tIrayS7~dZ~j*% zn&_Jk{!PX>yLZTuT+D?;AGYo6wDX-#KkbiSpT7dYHau=Xjlnx-lPD8XM@QmBM2X2F zC-Nv!rbr`~9!IJ0A~Pwf$dtoqfGS5DZ5gF7@zUr6p$bep)M)>_a&`(`c^Xh>@ZyRB zlL9iB-H=-PO$JL(yH&zU=zg^Ct$Gnl^!xoxenb8v&PpKr89XVeL%oXV3+RR!U>H55K`>pW=2 z1LXcJM_vX9%bdbjy(6j*Pedupd&tj&gnY+y>A2LXv@}JXk?RmG>Vy;yt;xv#P>?U> zVWNa{{7@=%|5GJJ5-t#JIyPAG8D9G&e1ANPif>ZpR|kea_w3Lt>_ z7F>f_qN6J{?iN`OZ7k^M87y{9BGiGfvb0A(>wX}Ku=m|7f=3pL)~4A|#Q|N*ec9K-7nat2-?${TK6c-S zqV19P1kWg$37*ZXR%bk!^9OF4YBb;>gN2KSWk?-^)=TLey;?DyoU5+@gBg03fqGWr z1~q7q)hNw58Z_za(3BeW!50YLF19|w&2MDQzyh_iG_PEIi`rH-4qy>(lXd6PpCz(p VV42z)+=Y8pe;*!EU#C?MegkxR{XhT! diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityField.class deleted file mode 100644 index a271bc16571d397c981e690fdc52f8afb516e83e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12029 zcmeHNS$Eq+6uxrPdTF|)8#INg3y_r70$R2@q^C=g2Hd1=+@^)HDE1^)BTJ4X=cLd4 z0$$;PU%+$Eq36Ie`~ZFq@XbiFB}a|NiBifrd9bXJ?#y@Zox9B3(Vu_+`Wq2V(05tN zD0JO*s#-%gEbWQDs%urdp}BfftD1JJrn$9ct)_c=!>%z?v-z!7XJ(T*+Jxm9-qT5g znYAnpD0EPCH+5@Cn_YOqs$Q1%C^WUD(dU?nf%FBFDU@I2P&0Z1_e13l*qrAW)>28K z;d?Ex)L_%rs^J>wbIr1BPxlPla?!m!V_R0sG(njwPchG94sQ2(Pn+z9ZLKl~4-`63 zu0$VzB0py=S-RJ9fO{hTcB~@AuawS@gRoGZ!SI|}GzJB?_bonrO`-i|K^e61e$SX; z8Q!=;M@QDnGWtNFbKTP1(H+PRCSzg^+`!Ca={0cPQOShk*iMd4C_~TgTMlz=bCuOp z%xu#zg+A1#V}s9zdqpkeD|M!FHdQ`AkxyXJpFp*$a}BBsObrJqs?(loxT;~*37uadqHoh@)SAs)mCM}Fy=qA`&+7>%awctY1} z2@39?D8TPE)~ifr+KmN!xj3=X(oOf8RlB9Tb#B+aWD5HE&YVchXDH!?>e;F;jJ}Xh zhlD#3yt8h@&vZ*jGU9vDsP7isl!TW>;^tUYZ}Px$_R>XrQN4O0Y+e@4ao@FE&MfcU zFkBar&L71e+qaegcZqo-$nX$3vOTDNtlb8kN%QV&uX4c;yc?>h4`w5?_v&y`54-?# z3F}yAJKdUSv|LZMk;fbZo?|;IA`L=3vpkr(+d|3~L-@ubHHFcXwRI$nolvKv}! zx11v1Bs)cH&_e$I&c8%t+N@H;NYj7d>C&+TkLgZ>AM<;dqZE+@>*LIEEDUQPBe-G&-$zqPyW5|AJ)6&}u_XttF zNfAlSKtCeNnPMMtNp3(XBbi24HmZi_r(|r(EZ1{dRSyZjC{4fla5 zH)WEukveYb1O8Wn?BKSGIMDztX?=d_EWshRX{-Vrr;~$pn2zw09OB?itFgeGieCyG zD%(}ve4sl9ZwC(tyt;up-_7!tDvrY5QJ*^&hrn(nfSFHW{IN=~sVRj8L|#8Umrx0e z*BeW`FsSt;iKo_;tNwbAL=qNm~v(;AIucWuoZ=>QC<7Vrj))4Ya^ii2Q|kQ z(Fz@cs$-9!$dldAqG=;X(~9y?MwP*lR}VGCz*D46OS4iN2`M7EIYLV2XB}O~e_@9C z63!9vg4v*`i%A<6kJmJDFei@kv@jCG{AZQylEj>)SJmn@lRJkZI5+I?tSMg66)TpW z2ReO}$o+UFRsMe^IoUZB;a@?F^)chI(7SUevMr~vT{@Nsu<~0GnFDI6G$=(R!FqZm zy$(D$QZJgcr0Vlf1gqTeoIgkR6gs_e-o+LHJ;LCn(5{iuX{?Ut?3PnyllVdg%QoMq zg<*&~k?cC!ybM{|g6B?9yq1n(*6=>kwrrhxmLrw+r`MhkJ|OZo|DJv=B-g z;&A4DoRI{vW+cTrPw&z|Al8K#PFt5z;X1m!m;m}%5>(rwa7&Un@1-eROA_?`G(o?Y z1pOdQ&>tj0FKrg|a)RyrED8G2WJpUHI0^IQteFZIl{V(j`_ND*; diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityFieldAll.class deleted file mode 100644 index 11356b593bf224c9f77bb95efec5888411774207..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4713 zcmd^C-EQ1O6h5;F*&k98(zH-oU`Pwi7GhKW>8=)4Re~aIQ`Ce=a5u4Mv(t_}%Xl0$ z@4>4eB#_{q7eG7;H;6MHn|RrUm^4jQapAScK6}17-#K%>nP2|+;U@rChfnKJBe0(6 zz>k=S{RixT`GJc3gpK?_sxnPPOSsa&fIe|rvZy&W#}HK zg%gi%8Y&Vh;!tRulRU&v1Fm@-Ai_Ya2*=-~_${PfAX$>ofsi~oOO%i!u${n&1rOOi zrz;q_hxx<_VLm;~r}tMry)2)e%jXIW6;G&*IbtRlQiIg+M<)A3*<$fB!YVs= z9EfP7v~gUgM#w27pWN_~mu1Z5crtqvj*qq9j7Cxf!q6neddCuq6Qk3>C{10BthQ}A zq;bdxBDN)fU+|fw(v+<|dN)ms{eo7M#gvz%`EX zHrRE;ogR&B&Q*~utPCuE>^eL)n9Ho9vcBUSSI+AeR*3C|Q>ZC3{f|AYY*dPCpBvX_ zE$tXw*34`J$mZx`I%>q-fQ?M5`PG|i$iinm*9H^o+x{mKNn(pH`>!nvxAbxft3VZ^ za>QoVX#WkN;<_lsJY>nx3drf_vIO#EqHRG77UrQ2iv%vL_K(ERRcFVF<3b<1%~l%i zalPZzbE&TaCU=<@cAUSQHA9i0M)w~}q>F@g2wWUTL2b>E?w<}scPC$BogSjiBf%N? z7K?C$u)NxyWP`xv`zdZ#BEA_Ph(uuUx2?8kN`bLA`*|Zof6Jx67bVqcz^eqlt+1Oo z)rMN7g%db8R$&Ah$bemQ7)x_o?8nS5R03D>*WFTld9m~`zN-X&J?r?+NHCC+qH8zc zEdsxvb(~f5nC(NKw8r568MmiQV#$v+4JQ_z_SXci{e7W)!89%`!}ZvTY4r;o!rFO< zki80=7OUwPWA?ujI4t8ru(IRNW&_?OWbyQfFNszG^b9H^f!4N4b-*|9X9ZWy@=?B3{gdZnJ4ucedvo!DuW#I6n>;*r;O%{qKMcGh+s z2F38M+j;Gv=qB(6V4>jk9G>;(@Lq=o{x)&6k-Z0K{RpkEmgc{Q7kx6!c)u|A<|2*=yaX>p4K8I?HFyQ@m+_j!xH1J}sUpT}@H)@%{XP2P2r+{6r2(|`Zo}dD( l@SmkND*%6r|Fuf~XW%BzvIg(v>3JVM!Epn_`v9+x{shkVt+W6D diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/EntityLink.class deleted file mode 100644 index b65c0fac61de3e80d54217a7852a598671a69a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14867 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ExpressionFluentHelper.class deleted file mode 100644 index 9804c91a54ec3212c4d6611ba7e1c4234aa74f1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4469 zcmds4U2hvj6upzCact5i4YWX8Usg#;p;28c-o;$^Zswl`VNxU;iS zBJmS=;*B?c1xO$P&-^IFnO!GdH(Kk6EpB#~AdaVnMl zBzs(;Nk(&-r&y?#zrD9F1Iy?_tXquc>)N#ZByQf@_v^aVl4ga`jW1GVqmJBEhfxyk z#qtBCv=tVf6Gk`deP=sqH^Y1=jEYn%!N(U51<;-6Sa+#?|6Gn8Tm1ZEyYW;y$Dve|KoBR-D zRp?1Zv;EN5jnQVBuCl4hmkPY;_)vx%WOt)DOOE4g#vIU>EB zNFE4B4&Rgf#EHi@Esqi&sW39&IzGm$rZiFoKxi7>!T3>%cOmzJSR@H=MzKu(O_Wdq z*agrP!GUN=zJSa<$P58NW*%hboyyG1Wai~ETi~IV33oYngbmu}7U6cS8P`W%#&13&XSp7*@1YEtk^!?^m!3R=X4FvQtq99VwV;Mjas&ev zU_IqAL@LWT7m75sDi+WXC){c-GRf4cj)D;;c}ydWh9lLgJ8t=o__nyjMwo6Q;V6hK zPg0Z>kf0K4(!grNbImU&+7jI`yb^|I>}ju7N#X_UZ`~w>l8ADEPGv%cbrU7s0DAPDsb&g#Lz0r zKvM*=R-<`(dWJ61GmNe-7Q37^s0J&p`^?s%_y=icPnw26cgQ+uAmUwNA~)_moOIBP zw(F&~a{~X7NZ}=*pZC4fX1^LzXo96C{&1Fly*j+FP6*%wDwKeJ_p&o!zd|@ z-ShDF$7*A_P*d_wy0TWfO5KVrc~({%&gnG0#z>XM-B`+GbQcBvqcewwhmmdT)NZG! z-Pr$QbJmuLM8%zhdWy4xsNX;>C@t!u?-5&=f8LN7)pm4h0=a?X0gh$;LsRj9m+i0_ zd8@sOJxpb1g~zB;&-RTtbH2-HrkGfT-UpM99}HpC+ZQ*`b`pA#PzG>pouKPlk_GqCg`mmMfXmT-p2D~T+@j59bE7I4P3HMP5=M^ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperBasic.class deleted file mode 100644 index 0cd92d66e46be8f287f1790f4d1064d25b3d869b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10440 zcmeHNTXz&i5UvRelPn@N zFFtzw37(_p;JZHi?k`clp4;wZ2d33AULQ7@>8`G?s=B(Sy1Kvr`I|>XbeSBP#w42a zT&-cLhTXWY_SJ^wSPf6@H8j%+^oFOeG<4NhEk|c&!{LvOE;D<~ZOobhv;Dd7<)Z2t znoJdmwueki)$TMFmhLmnmuQ3ObR^o@a@yKi{L zlF2UEw&Sb5;ni$SmFGS9oKy0&3=JPh90S0MuDY&zDGv(mW2Z04#y)6u!v zV{YHj*bUY1a*`oVz6%B*nP^>iY&$SbJdd}oFy9Yl+77J@e|5$&O^6RkB&xQqgG_~d zAeFmJ)tL+VHny&*Jx~i;)#ZTPE*F zbbRf!+(^XC%ui(oULn&n5^Wop_p<9c?l>vZj;C(hm@&ufGhKmK^$atdSf%GWu%zXk zSE>{3E>rx~9#c4jI(M6T2v~q7WL_aokDc&g6Fk|EBFet%8vIiP%ZD+% zK!fDVRX`@h3!kJ(MTAy!nN%{*-$Zzo_ zvG@|EUj=Jn-s+i#X84L1AYVobWP83FXujhrQ9P_o@^mJ!b+&BSOs^}~0?+4}MK}!U zjbZnl6{e_2J-S&A)d?{ZW_ zvRQ5px3o{i8@5xom=~CSdpg?Lc>Igcd02+$VbdD%ih_q|1Q45`VkL!aB}7yb*1y2& zLgp&d;r};%vL%|wj*4f|1tCdn^$1Z_=`bDJLI>zsiT31_VtC^r}QZ3K*!Q03j6Egop|Sf`uL!em63a zM6czntehg}P*XZ1=fsAN_+C?zz7R02P>}dXr}c$%6_$I~KU2bKz`#WiHxJapMU=ZN zn_-{evKoy;7bNNPb@qi~s|zhOA)=D7{+iScA5-oIHv{IbrUx41bm>2^eE;F|w#j4s zEKZ^`wN{e2O|@oFPzfxY*$tj@m-Lr|LCurDH<^hxXp5m7JTq&am`1jeBIg8*OfOa! zDnB?O)I;~f(9zg2FVSb~96J(YJ~=&EoaC^o>v5`b z!PaL`%yO)Bp&{;`s^t!R9J^PVF+%%%?zrh1a;EUav<+*fkK=ShqAO1}BtLo-`W{E; zu#3ZI$=R$l3Y$~o)RyR%CxeCuo||hW1)Aspw9^noJt5pI~le zf~9ulL@k+cHD%kH=q!_wxzZ38`(;|m+-`|hjZ79=*WPTAXv?*zV%Nq#+N&HTh=D6N z8|i8MHsMXi8~p84@LM^cp9+Ci=;9yKcP0n=Dvg~ z9E{_F7`L%#l9hZeRV*LpP7cnif;jI$@-ey>FQIo+r1#Kn?P5O%dg zxmSHcd$el%ooYb4RonGLry4j>P&tf6rn9c!JKHQMqql0dtBBDT&y&r<-62tRZ)(6t=j0w z!b1WV9)zCc9d^s>@qll*>>d@g!z2^Sp65#{x$gx8N{u_d=Y_6|D8&`rz5#Ot=C?ss zh494;%#%6mdrz>ycYDk+(R^L*MoP@C@KJpySTxI}W}BIEv&&4Sz*4KU?ruC`wlr<3 zV9gC?ZYi?Ov`p@5SWj-%YywxaTo%t z1$3$Xl(v|89(Qh`ag8Vi?J%OX-p3<#;j8%j=jGZK)2fS&#EVpiH9lba)V8;*0g+ zagFZ@S|^zAg6T8bv-QV3F_mGrExgV|kK35rWqWr^t6F0ROD7iJ)ylEK$|WtsjT@H} zjRL(`(b~ryEQ^!_=7fcouJy3n#lk1}feH_OH^|Ycw+lU~TUhgHY(rCQSqQqBwfx9A z(sgUj7SU{DNWTOhxq&>&y`AcxbnLH5*ZSgD1wvA5Vi;Q^*tM%z2 z2wnLN^O)dvhID#4DTzf?klWB7|Y^edfwC z%)*gbFyJVG;~8z6YSQK#++%mb&IS`r6`}-=Hhi1950jcD87#^+$6oE*#-w^!Z1zqJ z0%6aVfIXWS%2IP6wbzeI>bB#%8jjR4nw#?$n<`PuwJdERZDK|B!>WWbRqQyQKO}<79WioJo60B zRvry7_`-;_&!jXBSH8%s2YM85RcD||$cDx_3~R{Qx1Oc-q%@0SO|h3#I}

o-;d? zEa4*p_bZK5b&HX<$HL;F(}^<-9_q;QQqyO9Z3MIxrmw{i?!AGQb;Q|yB{P#w%hPMAcLXK|# zVg~%07W^-;0GImk9}H|?K^r8o#TICxzh^50^d~J)0w2N0eb7%b_q+@6ry0N-TEJ6q c1(#od&l7Wh0XOjd5FYqdScE00!`E>AKmQ2`5&!@I diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperCount.class deleted file mode 100644 index 65bd22998e23e60d42eadc51b8953ee687140dec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2962 zcmeHJ-EJF26h7l5cDyzHNok?9NYl^?8>-zKxFHBBq%BFcZBZJEdO6-bv8UOcS!QOf z^euP{-T(^`H{YC}@62C+|Mmv}Jb-&0XffEeCW&$( zRrFF!M3m?}vZ9EROqXe7)1xRAPUJepEYf}$jWH|GL=UqPmD{Z*JGxY^1FH-!S0I^C zqv+}IB_@tR$KX#TS{hxApWY{g{d!(4+G21Kk1;77PG1v7KfX`)QVA!u+8R7!aA%)3 zkU8$DiL`Q<;rGIb937f%rL?OIErV|SL@QNh8NsgZ5a$lO$KcW_rw7JpvksRS`}qfD z(CTc0DW@|RGOIv%VKl+#_A4HAr(ir0eiJ@KezL@AF1UMDV9;%FU1B+R`X%-UW8`$m zu}-%5lo8?nx*-y}`nizWLvD zZDi>>++y&{Tg$#?_^OIJ+siZIK8HPn>-_~s^f9>A3{6=SR2%k3VHCy4lAqQlf)v6B zCe!&)A4OGK35V&{I&8AF(K%e;3!qAJp9G;-m9SQo1&0jzp5xMTsZwc3c(PY@A)rdA z#H*U|LPXzE9wwQvmLJLtZP0ypyQD}PdjeEDM`DD0lcXmkxg|m*w;{=Gcp|r9CAVQM zx0^iGXt|f6T)1Rhl{Ow1bkHUo<$36}JggjcZt&U%GB31o+%G*2E6J-RalUM4?6nz% zq7s7zQJJxv^7-G4PhDMCMp!Fnj1$b3`DVJpHv^x^+h$~Uw6e~WiF=Lo`gksY_5I=B z7FMG!F?v&_*~^PeCem?R(tUxN>?zHk0&s!YG-B2vt1_mp1<)mf+YtW|A3XMCk4lU@? ztixy;(BK07zMzjS`VW4G-iwWkzry7|Y1V@G>DeYbfO~L_p1o=uz;*b5W(+=r8)Ws- z)T)W**VJe?7odH+01b%f#|<=22=m--FF>O^0ol%STY*pLT?_7)QRLOEf~c6^V~KG$zDI#Kh(rV+UtHy#-^zuDwrnbEli9WQp; ze8t;xB4^#@3!dl4^0|o7`I_{|HNB>>)(L`;N6}|y=s8|9!o&NwOt>I4yMBA!-!yJ> zkB9KX#?5Kh@iv!3zkS~f;pP%x;>V1twK3gG6x5&;$_r~w(=+2v2=wpvuCa1;0y<`A z8;OmL@_bqS&FFA25QAIeqCK?R2?Rp0v}Yxi*?H(WP;opbo@aEWq-Hw~k>v95%FIJX zmv- zf#b>&)Pm4QO}3*stypNZxE4PSxF!_nq9*nF`Xg?|nq`Wj)z-PTqeRzMV$F#($FrRf zBJN|{+TbWe3kVyiN?d>0!982kE!T`9ZNqVS^gK~)4q#zGU|O4IlWUjYxsJ{OgU~s2 zbPm1OIds`M^rUlK(rlkcn((=8##T#<5gNW7z)@8lHkvNlk}d<5S$PrzryclVEMvW` z%lIZ48IJXyQwFtq+#9J5c^t8>#lB|B zOv5? zcF5rks%pw2Gf|_mnZC%Q*a1}l5lV^Joj4AvuEV`i14Z?8LH4V@_{{~&0x|N#5dg(P zeG!!J0fr3>dBCHN8%vhtSRcJBVAN?{zmn+g4I)9i>(94WYji85u z3?2g|=c}FeIu9EnVDd%7GTnz}=!om?VlHkuc%!&GE^AfGIw?}H%F{JpMFKhOqVdVW zR2nLDqpXm8yoWS-@6BehG&3%$%Igo6W_l2ej`jd#e`RzMD18Pk7ZHdH!>g66JvD;n zPb*LDZ0_`oSO1hrqEJvKosM>N|yS~Bb+tS|q@3qXo~%E)&`+<8BSr0d>wll@{h}@-(8HrzJ**lo^<(+u+B; zJv2`%11F=B&*bSYqvL(Dmo$?+-5Xdb zx9Dv+C9?B$=WdL^sAt4@hu-Zu_ufE|d-;7I1~3wzJ8|vjcWwmqSDB!vs6rof-4k`8 shOeKu`4RfR&ZIv_AHm`&TIgo(R?oRA*6-u*DC%($^?HXsrW&pO11YNClmGw# diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperDelete.class deleted file mode 100644 index ff335f3f9aca3d71991863cba9f04d70e52649c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7666 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 age3Rrr3Me^J_hp@^dUSUyH1`*Q2POuiP@?E diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFactory.class deleted file mode 100644 index d999b0e27d35458f7268c14f845cff3a208ded56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12231 zcmeHNTXWk)6h7;w@ug|grWaaTs0am;0y30xi%lT+rZ{a#oRo5{B5fQsvZP8Yw|V9b zhGB+(z$-62@IVJ<;GN&VpWvNgI4jADTq~C1k<*arLu^Y~edp}Ce7k#e{`~jX-vM9- zYFWq-xNci=u`a24@vhX6in38J+LBq6HN#Pgwz64NBqr4jg=$5E|154$&7@XwR&%J% zuDKVplFSTiD+_%Dj=M0Lq}Pf!*X~l85jbK~tD(wtQDPeehAP`5mYJpMwGx5zOODRe zI-S=Ws;#bRbVAn+CNb5}Z36kq4MW!*O~a9c_f@vB90t5I(vCPC4vrJ3ujm%F4Xr^H0qtz6nma+kv<#ea-5wJPd4HmU zEO7$}Yg7mxVTK?HY|EsDyl1!0g9>mIh6dmyoFdR)R&|w)6F41Zt5wvuqU!X9Q(vQ2 zRYGOBn+#dfRwYa2*Z$4CMI~i&>qELlfQ6x^G|9G0?S7~%NM-rSXz~yKBL#O5KAfUmh=opvOX% z^s=Zl-e&G{pw;*emM@23{8S6wX#%I{pQLun!39F})Y8F?zRuZ@VqztP!pM(NDM=RR zJujtzM4*ptsK}<3G}6SUd`!qb&Ittcw)l()%2b$O3FVat`KAZS&!LmX@IKD{)XbdJ z7CCsHz+eB@d`^;wxQ5CY=nPlJAHtE|q}49B8^HpQ4hc`WDq?a>_aHeu@Et=U<<+od zz4NUcEO@B|cc$#kU#D)Y_5^b4jxO_JCV_89+?rUZ4%0kBY?loE zYTB&-P^mJh2ES9;`UYqubF~LsvL<|6s(Qr?ETb(^+tFAROKASBh>Dj#CgMMMHo)pi z!s=GMrOVZ(URX#Abg!%|&6g_I7&BKab&1|{sLfE{kT1&JZm+||#X#PpCC8>tAzM|Z zQ3n;PpBPDD8>Si)Nt~9063Kkkc~iQJa`s-9%F-*aCp@dASDb8yN=z{j<2GhQYf#4w zPG+!ehKhj;g>cNsWQW|Lb)O}d@lBFO7Y!TVINeW7vZg?Hkf+6YbmI$1DG=|)CzD{7e z^Hg}XFHyUV0ea+Y`&QYCOSm01fIk-vs zlaNYvgSs6N0zW&6yy-;r7KsQei^4n;R9jGWMYZr9zP5#}73y}Z+>w6CV zPZ30!_cI9Vsz&WXe($6x6a{kyOiA99YE&4;J`oZ7aZn-b#}Tm~M-2LLM7JMD^!jmz z1;wDYzPS4U2iU)+0HpeVoHoEy5Z5 zY2X9V?X~fGaoYQC+Em@mc*ClR5;_x|5rf=|k=(yE!@zBEob=s|#DI(g^m1C9kY&vJ z3T@EXDDa=)CvBdC6IRWh!tO6-IlQ?nQXUWDh<^y7PO162sj0Hc1lz%`HEg@uM)a6s zWR#PFqbqbBTc8wCSa6V}F>~C--O@Y~YT?3W`@ST-C>rnAWU1kSb;X}ktUmo*8TtL$JuT-Ev|H{!Ff zf_U49;w;?4KFHA7Hw(A1EI8|*U9<2hfn%-5@+^Fg%{6?X&BB)i4&kz&1qpxP`92GB zOOK(u$Ik+)9csVyB7wlbf@<5i=vJ|H&;|hz0{cI4;f2G{kKb9m;@>&^9>mW)6d(gf z@G49E+XHxU86P9~UHA=--X0qK1y1~dR~a~spZsw^=rise{D7z6ERH|tUO(-Eo#c;m z8R1V3d=&@w;kO5TJ_2~u1s-sP6yO!{y5fj~tn2ZqdPo#+HkcgSx%wo1(1nG+uajjbs^AREjBSm00HTt5- zm<#C5HlA$F-W%=E7%H46Tj}f&EaJTkd=!8$;q^NHok2RT!YX_WpTKAE1>Au(R6+R< D+7M1X diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperFunction.class deleted file mode 100644 index 648174f547e6a38120c19b493aa79b0b70696737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11111 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperUpdate.class deleted file mode 100644 index a16ffe7978ced894f567f2f22b26042e008070ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12084 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 Ir-ta&KZX3sNB{r; diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataEntitySerializer.class deleted file mode 100644 index ed3dc5b3aa453dc812009dea4ae1fab4189e2abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10098 zcmeHNZFAd15Z+7D+HumCwDg6xQ1t~!Qfe0neRD|3dtDSK#7UTTnCV=kv#nOrnUj<@ zeC40Ok6;EE_{InR1|Rte%rNXpaje8@bWJ=RciIn@bk^RpdwaWkd#n5Fzh8a_fLU0} zKo0@k6Gp+ImRop2H)z4&PQjy%f?@N(EO_SAf=PYqaFf{uE*}eZW;d89EY4tK*7Yra zv%-W$ZR>j`GSElhcnD-uw^mqOf5Hr(K(Ebf1WxE&)C!(m9eYyHd9B7ok-+)o06`p9 zayKl`TDRGx>vEs^7I!@Y7xWfbC1^AR^Sp1Ua4omyVdp~|)DDtCre&EIS=O#A-RtT=g1K^uM`*p;#C zysF)si1Kw$x{)MyEYFicBkZ(58@YZ3kBRm}o$}&N+WISZR5ga(Z^+7%piG!<&t2mh zH#_GVF(36g`qN@n*+Fk9N}=R^3PE3KiH(hCA2H{);*eroURG;&6j`V_YL`;wUK2x;3~H}YEO=pi>(4&FZegve+|g2+ERA{1 z!dg^ko8w}&fn^r96kttleR#k8a3PBTi+U@H7Dw83K%l>3)m$nUI|9!R&TxEmBF=MM z-?=gtCte_hzGXzHl&ibzg-cXWT*8@{j28}bdNbcHX5lI!KPpu1)GSpg#Hv9xMNC=t zTa%PRxKo=c#LBCVLflJbtgW*&Zz+)@A8wJnIY zcEyoLS@?{Q;|iYmMr{TOTvH2XGtBmB7H$%l-&=?BJG#=!OW>jrW{FEOn~1c&9XmsI z1in2;mbW$addu-K-HSB{JU%#lxHXD_?&5GpFo$ojHHIq!cS|P2Llaet3EZR($7PY* z)}e@Y)@k^glS0r<+?<~91>L~Tti^)B@Q$wfl(mH4_$>ny?jkm(@$*{X;2{l(X}yqi zJY+wMM;}n8V*4f%cYH0Zh`6cH*71sHQziF*$SYUjfiTz{o^l{?F7cpAcus&oMh`z5 z7Bet|-CAriFh^h@Hl7UJM}k(0mVpNZj_%yHWI&gPM(|5X2Jqa-fYj91urjcO-CL^5 z!19iBE|K^dSiygHpK&2DxDZu@SMWrL2R8r@n&1JPUN{Gb;4ro_*pl}g-p|1R42Jh3 z;e7y~kHRs0dmNv}uouC5?pGLkc;e*GaOw|i^}t!YN(8{)yLcUps1WTuwz6;m-h)1P zAD?^R;x<1G+n4ZnqzB1mgTQ6HN=Bo|s0Z&6qbmuF?uLvcO%^_Y54YhzYJ<;^E%+vI zYXrjyz{@e(nO@jYB27ecdXgboa_L%r2kbh$!)7eo#cp!%O(9WFp^xVGhoF6|we*=I4JZnLN!G^PGoC~SrLvbwP)a0=fMVu;A zX0gkT;!Jpvn+#QK!f$+lsz4j>XiN-kL#dBiFwJ1Ddar~Y$2+4#Og)2kKUGpozs_K4 zWp$6i^adGfL5IQYnfAb1W7}|>HQMjAMQ7A8W}E^kq!L~l%?oQr3Ujx{yPf0!xj!wC z2Ln-X7fsypRA_BHA0hv3B;s41OUI?oq@{?|DP8TOMV*p|eQR=(Ka_Nr@hDZoIleCy zy8r1VLvkDfC`5WB#>kf`^N1jzfCz$!Ac+1z5LFOF69|@hX3+7Fb1r;(z&(`)PYO!( zyg9-*3iw7z(Cs9@32_lzk-R*1}txKh@z=3 zW*Id5HH8*ehG(SN^;YU}FOX`MD2Ur-K0=!a;t7L=p-F|>6IKSfK5P=S9xa29E5m=} z{NSV@YB{3hrYceUS5+(YR&UrIgU+rTYvD^v$$Y)xaQjJUSgKWAOZM%{Rh|90{`vPrAFNv?*>`{a37(QX IO}A6O0My~x1ONa4 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/Order.class deleted file mode 100644 index 70b98be695bebd8bfdbaa06262b34421415ccf24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 943 zcmbtT!EO^V5FMu>+iXKq0xc9SR!^-|d4JK$Hv+QDWB}E{x_F{BK;AHqe+(!hO zFZ+?Fb*zjECj@r(hqt*#!6`F48XR0A@C*w~phKX03+Ov%-8yU%^6{;4(p!BabBY2> zrFl@+(9+q7mW6*#)Ao2SX;_q!MgkVqu%#L*xG^@+iKM?t7##$vJXI!F4h8E4UQMNw zMqq}ivjxWQE4<4o6PkNZr%Fq|%#uqyidQ8U*L)`F9@fOrbcCYm3{7W$OlP&}Y-#!) z&8_q_R#WgG<}_f@@y`dFcUvj5n{Ohn8=E@UDN49c8zCzP6s7JLn1NjFgrdB>k6J9In zxD)e50=on@S4Icb66l;O?{UV)I2Qi&Dd1e=kJC8ag*(s!z%_|0o;R@gHm)f~&kz-H v=@-~|*Sq@(y5BHrKo8$7#64~1P`%&x%2(*<9Gh?^Yd2#*no!~)Cjz{CJs|3RJhL$Ss3dyv@8o_ zsq-YX$w8Pf$5Nee8S3H~PPojt35P+NQ&S$DT0y|K}Cu+nrLomk#oviJfRLt z!;dmdbk-e`A}AOalm)x6^Qs0S&9rf4t&f7TzEwy5thL{qws}q;sMC~{UGtk2f&Omf zgAYqBxB?4Elz~uuD^Ew`0s WN_AMpwFbB0PPyNO2e>!z*1cb<0r~v^ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmComplex.class deleted file mode 100644 index 05d457ca757e19375a21c1807c4ebebb9b32c48f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3158 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 JrF#@m_&-z`vzPz? diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityUtil.class deleted file mode 100644 index d10456584a8af7c5430687db32483235ffed881a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3311 zcmcguUsD`K5bpuPaR-J36Er58DU#?NCc8o3a#v>|0YlY+CU?Q|!G~dYxSO!MvzeJi z_z1rG?&q*dtMuIuW!bZP9J?H;+Y-tLZkd_x{`LRvzyJCDPXO42wF*oTc&?S_hD=26 z8+OE8Plm2$vFimg@m=j7x;`^3ls*q!Y5#QxJczk+_xx}(GQym^HX^9N41pV|fPh7P z_uKt9+%p8`c}lVoFs%vP?3_`gS4{%duINY1B#INbcmBQA%OHBqHj>Uq{HN_m8y0y; z{Jh@iTvl}}wB8tj5V-p)L4`xU9UTcR_5;2iMba=MWQ6)xJG(N9k|037nGJMF1#S>n zJTq!jDXHdQiI87kM~Z719C4qbOJfnFLTIccN)7chTB{C)QIA;=y3gr|#YoDiIf;3# znmTF%P39FNQjE=GkH=QyNlZb&`z%=ZJg#+Frd`=hyul7P1L>FLL1#)YRjtQVpPRCL z-GNL3e^;6pERK0pmaT-tYOn~m7T`846PRs@NSHQ(+x1-fb&V2HzuDNs_;y6Zcaw0R zs~*GP5m@S=J;5GR!k&khGiD$#N474Vl(wEQ!Iq04m;VRc8=HMYAZ%Y*ad7uf!dUd_x>Lm3BoTB~M31~5W z>J56JWJnDX*d|8Da-!w2*!KJfYft0>{p#`Q@r&Z|%kW5ka2;?)>wuH415UOMtWaNaP3;^Dnehf`p!ji&hUUfLz;^7e;iL{L zEj;sq2xF-X9r^kKCyx;xJLz!EhVS^GEzl3unItw#7w6S5SnH~V4DL;PLHsNfEKgr~cOn|pYw!pHCl z%nWhv7jRbay+U%Js|C;pL(r)l=+hK*3K?;af=43*J{<}E4Bt)-uymOL^$QFjr3{#X ZRg^LXpASuVJVJVc_fPR}7GE8h{uke{6iNU9 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.class deleted file mode 100644 index 19c45a24a1a651c8522c070dee1f6fab11c3610e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6797 zcmeHMS##V(5bkj{*&`+nb`p++pd5e=L?TrLLhLd)j-70=KFSvbZ)0h^JBg%GW@fkS zpTGmZhbpLocYX{%fuctv+ge-6DvK*cfgiM^qo;eid#1nc-ar2O^tOU%i3OZ4#G329DpUx3 zGdwHHg}}KP9&OZ4x=G;WhU33{P2wmo) z%iIH(xv9%s>$00pAeeHD&Jopq$I)mSwws`$d3NYT*X##3HCSop)&@LEgw)p7Tojmi zSh8I=^_KPTanJR!6|(J8G!LqGVw9O31~EJ(V?m!UPSzlyh_=Nucj0-LhH5Sjo>0{> zA)?SNX8#s^aI|n}8WI|H%WnXY;j>$U=CzRghgUqnekPDdOm_svdK#x&VA4kNDI1zi zI7=gaKQztwG|^qjZr!~jcAT$gZAe#U8<-gpSBl4NJJa%O)XQwJ{61P1aoi2V0U9*p zq^sApWHcH;x$+2+c460NNs4F3qgY;eO1y?R*p)t8rMgpw`2c!*hBI@wnYQ>mFTxPf z0wH#?z~+`_#ts`VwKEz4ZQiI`J<|;0TeerQ?xX8Ksv^2;p(Ik_*Duoulc(K=d zRC0=mL`J`c(9e`!V>EcUSG?Q;x4hDv+#W}xuQS=>)}@O=E;7XIBISp*`zz@G2=(Ui zi@-Opb-xC=c_*Ynj$^-D%AS@cZs9f!*EQ9w?3^cBC-Wg^aZ%Ek*M2U|Iu6P2&G|l4 zO31>kogLh4hp29-AG4TA?iWz!r5OI!=>rJ&BSRu$Ra)8RN?EgP#;Ti`e2)bySQ4b$ zqwL&amezn8yfF%A;R1onlP!bGURn{@a58NwhOrip*>X49W^%)vT>|4R;nQ#{JImS2 zitcbl;BjmCk|kIDOcS`$F9i#ZOw!uPh?v>P;_CD)O1rpYf_igD!>22WwIFHt>(!AAZ63WL}s!%P$l;3Ft#li=y89|&B^f#XV#Ny!6-h;S|U zdL5w~`(ElhDr3yW6@EV3qhU8iOj~7oX*z;=Qh*eY85gGZZ1dg0m?ngV5`Sh=Zbxww)bgyAR8k(yK{%w^zf!h9(YVa|E8!z9T z*ign9oviDWcBz|~XsxHE_ae4}2~6~NAJQ%=CR6LYjr9|@AqepOI`u*BIA(`;_8(-6 zS>n-`C{DDFwKDK5*D)auF#s@$d1D}!h2B)`G1GXMF;%=r;CwEVtiqR=a2_VxcBKSH zmu%-`19M$fxdCVm{96GqJ;z7_=kT|RPmB1L;Mn*TYTu7HeumNC@#zeV;jf_qye{JJ zsHK88VH}?(`rcehsV>76oc9*qSCIB?co**shpRXqmvpT_(*2T>uEYB%$?$K$2sk+2 z!s|9(Re}OPFgJJ^)kbi%s(n}>@OmkM6_`8_XcQ_TY-(s>1U`XJaoI<;+gIq&SHsJw dz#TNp2)N*-<7WrHx{J^E@v5QeK1UyZ@i)c`tepS= diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmObject.class deleted file mode 100644 index 8f779e5a8407fef937dd0d8860681031859b3b39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12571 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchChangeSetFluentHelperBasic.class deleted file mode 100644 index 1ba22e911e1bb885fcbee492d8a8486ad16e0017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8751 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 P!wz)eF@zAIRLTDU6vm2= diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSet.class deleted file mode 100644 index b8905004ac283e73a7240735e2361f96a30e69d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3499 zcmcgvZEqVz5T13NI6eYtowTJdz&2{Z=H-sJPo%1phEhekHYf?H__)5?*xTIg9=m&1 z`+N8iNFc#?{t^;m_U?SOi5nX=;lsVHXJ?+7otbC+=ik5n4ginfs}?i}>>C{gDHAey z&PFVVR2mqT1yQ2%I56=^5HrhC74sxe?llCHE;raVo z!`OO&HgE0c(u$Pt%8@XlpYUxdl`UW*w3YBNx0Y)ZTpFRNENY9pXAtIFl&l! zSSCV_)Cs2y(`q=a!D}zz?gAJW53pn6@qkfR|7_|`og0{^IJR(Ui&7909T2V-SRYT3 zMOY{0Y%r;!W@CTGVAee~u=Kq4+4)yu9#w?9=wLKIY&ZZPs9Z<gZch484|FZ=-u(l=8gLu$P8Gm)cn|NZ z#k&XZ!w2{U2;&a!EDdiKCOq8b4By3Ttl8}^HQ$?|d3}av3?zI6%}R5(2Jf`a0pFWp bg3K^+2|ht(4fwQH{uzFMiPti&`tbQbyY*k1 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestChangeSetOperation.class deleted file mode 100644 index 82faaea9e1177035c8afa7fea71533f15fc9dfea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3853 zcmd^CTW{P%6h5;}yBAUdX$rjo)0E3@Dr_!&VxvlFdW+-|sR>00@i1%8?oMORxHIDw z{R#XcB#_{pAB8w$@5R+_;*FFRBp%l5<2m!4`&pn^=7a2EpcgW;`A8@;m z>&yxz>##!Log6pDHGjYNoQIacyWxa&S4op4ToY&ollLxx=7AVUW;4wRbfyd2!Tx>J zU@R^d?H57~x81wAZ?zw1T-wLEiUckN4@>ktW<-cgSLVNNZ*6V4{TH@{6!tCw+MY`7 z&a-zr&j{R~u~aEGk5CA7FxQ`E(u#!d$&oOk7xNt{mCfB}2;2x}-aE+BRC8m#XIct5 zFsSeO5kmtBtOv15dg{o3#HI5+k8p8i7o%2(%LLYDAP=-wx&iN#`Y+!}L{ssIM--!; ziZ~ZaQ>{>o#C$_r&BGz5_GQYc1Mpl7y&{G*WKK=m<8%Vz(LGCrp+ZJNW0>QYxYXyG z%Mc~>wMvkFk>OfIy)b6R(7uSdd6QBij)I*6DGQI-fYVL1+{1iFg)rYe%y(~^?_Qqo zUXkx!p6{D9Qru8ydEzpkT67TqIYo2Jpjc9#D=9B`+bKm9{t!v3v@QIeSrIRA_$!QK zA!o!^WRtfOhL~>wAMr~0;~kH88S%c5Jo4z1%vhId z`8KRAAxG+nQ---Xd=;V9T++yC@2pVvw8kwT*K+v&;}Je9^-k-|e>s-Nw)=AEj;;DK z)0;UQz+n}0t8BfiiO&24jrnzL(f4AqZa|yB&6Cp!2VA^%9H>l( z{2@-F1a6&toyo^J0#}1$4meb)5fr_!TL%xRNs`s!3%vD|t;#PWb@&P|0COoeyVPJw zJrTxW6F9^v+yDgv=kW4YgLOC$7m%vwR0HWIw5If1$c201BK{W~Z^IgLx*hBIe+hqg zaLr-0eus@8*5CUTuKbBq4X)$U@d5sB;Bz&9Z@~xfAyNPzp%xR=n+s9h-U4-V0qVtt ms2{^8Q`Ao@P(ND|^&E6ilNxMI@oppiHU1hX>vQno%YOiKve)qd diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestOperation.class deleted file mode 100644 index fda83cd23e0672483ef546e7e63fac0533927c74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1121 zcmcgr&rcLF6n@2(-5pV25mdZ1#KY>La~ERbk2N7#!N5x5u{-mCLZ=hB00!{54RZ|kyeslbDwChciOdUI0#< zzC6uK8DwQk)VYn<{1|nOE+5NK9Otj%xrb*B`mjrG8I(0`Xfaqy>9jOQ`R@1xi@>0> zRcK{Yc){RS|LFmP)@#b!hAxA}tLTAq)^*?xn;U#E4!zYAEIIX6E1lBw+S#$j%0K5> zcXW(AT-3;80ujrJX*pg7f+`$3TakU%&|Aqx zp{4iyP-*o4vr36nI5tqr;#3|X-=N6_F+vRyBZ3$azZemT5iw;%Bt|xPY0>jIc@??g zfdInun#TP-DUmKB=_0}Qk0qvfsH(OuB;3zM!g|Wib=?1mpVhj7o~*lLjcK&L;f)8& zWgr_cCfU z;pQ21hcf^+8D#smaRt65{V`a-am^BUZFt0B`C3^93-6ToG}efg!_NcI&e7Hb%n=;7 zVV>+ZS6?K+Da+xEQpeZrq7Jc^%i=1eW| zc+T@mG;GkLzG60LY|~4)A1_P#22Io!wXq;RYfyR3-SSzSgxsKu2LSHQ%*@;aR4G&( z3nZ%#Ae1HnUQaT|Sr5`@^NH&@JT&M+bswyhe0F-npsP<3KX#jZ#ouuwx9;&d-w$FI zyMZ4Wbgm}#>M?)IUftng=sFxUraC8m&qCJZF%S36F4rChzMps=rVQVQ6-qQ|(0Ci= zLKp_&D4jQG`3FDbQQ+-x$AlGIt|wVATVVj6ns>}fIiqhjm|)SYb8~M>9GJ|EU$l6o zLKAdxj4sh-gGTPUz8l{&=zLX^V=WF{e@hBKUUPl^IBC{-xXwUFs5EFWZ-a%d7^e@1 z<82qp*r@f@CsJ>NZpOEGth)pBUY$N*e*j+2d7Qb$HC@D{)q-xKA6e)_)(BHi?=U!= z5YP*n_28Mk#C;yJm^-th^nr2e*X{<0gaw4*x@&)MAiBQ9^69}>=R)ZLu5{D`XnmWT zaQf}QG2=jaRdw17Hl-t)ox#OY!NqJQQEUe3S~7#sbek=BIAQ}_I7$$mgdeAfE9F-! z=TWiOpYlfBLoG*C^$}SpWSK%h{qg1_N?VAt4Y5?aYuTU*f<_wURlMlr1nYX*p!d43 zYbT+h&I(3_(DWul<7Y}*rllcTY z4JjwQ48@G=0>bLFvuT~Ab%pdtVjZIgRV z`ESYc-%{njRTqW-b<+uWWQtO$ zb_mT`3;W_)M6nKMsmUyU!+R&EyNK|(U%4EI?cT$KopyO3XYmpLt zl~iq2$=r{mBhzppfi*rlx}icr1Z{iKD6O(lp+Zcq*c|T?88+=V`_%CaT6`f{55bNh zO9i+tWY_(o9VI>Y@H0I@wyT~aDf$?3vz^A*yyzDnR*UYd2Q&=A1%Pguficrpmr-5AYjPSzyz8!PS$8}U7z@qGHp#l99+<@n5c>amSk~@kUE*u< zwTz7(DOGp3qEiZa8x9q?Y(F{<6;#PQJN+|GRC4ypa0VjI6R;)sxiKDF2J>SoLt&0>;$LFpv)EzsXVou1{KoZrj2I6sUQkR^T7lAQ44tZjpP^ zA{?@7r4^z$mC1rFZZkC-bgDYNg2SJ+APF0M5l0h9M0))%iYvKQi4O&8vK+zT?_}*j zwTY)?oOF9@;=~c3C#?854!$oAJUEWJN0!nrA?6G^qwl{8^d-7)&{;jjN(M@_0AT&G z0$##X;Z(Nzia>4&l?Xy;Pt`#a*m$JQEY@)Z3N}U`un zNsdM_E>i{fqj>fPjRERSj0D76_#ekrGB7xUo3prX17HaMl|N|Wr-^gF(S^S-8lo%s z6_bcA(^dSA$@>ajqqi|K=pA|&GZj&DkHq_Umthri71pU7tP{CdKcpL=oiUt`ty&n{ z41JVhdkee@@o(aKS*(}n<2}i(TwaA?l;m~}uc(OdzEhu{(5JvEB(H#H!K>hN&s?8j zkSVrpuG=~2Cv(sb(-(W_g;5Kj&eL)pYJz>% diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchRequestRead$GetByKey.class deleted file mode 100644 index b6230206b22d1fbb887c9e3f5db34e6c395218b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8147 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponse.class deleted file mode 100644 index 69032c20370295d47981a3cdb662cbb840f91b0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3825 zcmd5QrV$}sT@ugPF?l99bFj8Z2V9QKDX*RjAw@q@_n}mU1 z=8VqhcYl=Qb8kY}WhuK3RAq*R+vnk5<;rdQSCbJRc4d-snwtomw#6!egsj?#Oc_yMW0zM#k z=onH88V3pLlP`@LruP3VCghpcKg=_^7Ri+RNaHgkSSFcovbr|?L?%fdofX_(sg^pr z6g|6Pyq;tt8>Y<@X?7`q8&S@a>|4`Hwz-cwAE?@1Kc6OP-qmTx!!0x)lSk`^0q>f= zaQ?faMRL>#jHa#Ey+=pi@nLa&_5O`!_l`*RBfjf{ov_!3jXViP2=%LXq;_cP}S4xD#x`3W?G0!>@1)>{AeTm7vV(#@#UKZylOnIzSDuynNQHdHuM?vjy zWrk&;t5%~ax;jIz)2v04&2ls^)V-5oypt)ULPHPhSx+SVNglVkYU##h(VQm(Kic(` z(9h-J_&(R8zP`ESp(`~kn%)uniJ#>PZ~RyuH@(Z-Hr6^A`h16Hja8uC+Ro9yV!oLi zh*ZF=hk6SzjUt6DUYY-pyb42g%fZ%?M=<$j-Rn(tebrd&d43NU&i9+4K#*C!mMew1Ud5GS8x!??sxTEw!-sr;`>z3}0l3Y_zj0G>K;$Ps`GzNQB>{x_t2i<1BV diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchResponseChangeSet.class deleted file mode 100644 index 2916d066fad3e3b6e8dc720f77034f70213aa6e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1217 zcmcgsOHUL*5UyE~eTagqD2k5`C*aU?mko&$Oh`sdSS0b-J5%g7^mLEiJsbE5{vs1i z^zM%`R`)Is!^Xsmhv`h!eErqqtD2v`zJ3RQU3l4qMgo0j^RyIFrzc`8(%h7(6IGfk zQx~Z#PSZkoQJMl(YQk$eLRFzn2g2v0^i4Pq&{ak|+!+Zy#6J2a%q6fC`4qX*_k$D6 zeFCjsuB4WJJAqs48%GJu?GSMj+6gRPp?9q{wgq>R#^(>(qBClY1*60&sf3qCv&x!* z!qUBBo%Z1fnLn?P1p*$z@kuzA3$2Z317sH@ob7og9h16{mNHW3^ywHa>YO4RTT_z# zsitoQfu(H>cvcI+^@?4+CxEunm{)*xll*K%3xnOhv;`K3eqK}Akoo^tZ!r??X3jbeL2*^ z*OmxYrtV&LnQ(7&duy9us|Pg|P~x5*ODBo`br}2DVd86N(^e6((>Jxv@hy!sU7NkS zaUPnmO1l}-Xu@;ixQv;=!aM046?#Z3=jIcjO{Sj-K!X}|8|KMwlC?SYcD_ODW4H4K r7Jfv`5}g49^xTi0@Tcg)1G@h(+RN~0ig_IEm3TgZ7tz~*r||4Ix-oW` diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchService.class deleted file mode 100644 index 34f550f8e7345f13bec8f75d6d4de405a5d307a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1080 zcmcgrOK%e~5FWQ_lPrZkXv?D_71WvsZdC8T@FZqP$!j`$`RW7ux zi`*AidBFoOZ6S4T<8OW;btPRs$a)v};Lr4TeBG?}ySR#T=iC~F{U0PV_X`H-_l+b_x z$?FQoy_*!dW|3=_VEe}sQ#@2zSr-!S*DPT@WoI7utbJyUskQzKCp1{f6N6a>Ex5k| z4`7wRa(5<_txoS&8vWpu86QUJtoD?VXLUK0Zop9u1U7nBaDC35iuTO8lt3Y{+1b03 zXR44x0`0yU8y;$h_qRKHgA-j#6J94_Cq)l1Xy;vRf-2>ynJBL?_lqd3G_1!Iz1eUW zne}b$ggimf5Lmyv5(#Gl&%5(1y>ns*Ucd853?@dv1Ozf1s^2)7qt0byH$Wwe`U z?a#c~7ifLh$Uec!w-mF6?-&F4tS3)g(hYcs_cxQh1>3WjNBCWY$I1I-cKQ@{&~C)4 FzW``eM%e%W diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseChangeSet.class deleted file mode 100644 index 0c47d77f6a6ec6f0088937d64d6df9f0bd5a9405..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6351 zcmdT|>r)&>5bpt!dx%LSA!3L+Qa-q-yYa0##3TeP%1dGeL{0f+*d4eDyE~VeS%kmL zDy`D*{uBNumOZoFW`Pa27ROnB*t>o7{JN*Rr(b{m{p;@luna3T7$fjltH6($Nc<;k zm-&H=ea(7)5Xm(3b-3e)%&=I7Jo2Tz^}9Ukapi9^6LkH1e2b-#xoaODaov-N=1X0c zZ1Xj4YA`|Il7o%#mH%+_2@mk?c>-&v;#$s*z~#WP*4#Wq5t$Jv4uSKXj?kt}ps^;l z6J}Dy32^svp)(9Qo~B%zeAhYa_9F6}$wgXDj4=Dldq%OqNJXI8SxJ+?3W;<-=MQHq zQ)eTLGf5V1EIJ9Nwz*kSoEaXv4}zo4>hrC>)@BZF7mhETeU##+V!oQ}3N1DxekVz! z>Fd4Gc_5P{jUwFMl}^D8cDYg_F8$T#05ifb9|@{poq z_C(~cX-`RH7VBHI**M@q17>;CO-}PAMp8!2evdaB4-a?_AhV7#fF#%70J;iLee(}M zs>*RmMiHJbsdNcovkB91Z4zeTI-ZCHkqEO$V0yk_mo=kAvfXZNpfx%o;SbVyldE-x zswObikpYV~m=gB+;N^tr3UrpAPIrnXa18cvJ59U-QJqA0D>r@D6GGP{EI_|CiW}7MwT(A3!vEzpEg51(Hge zvp9PL6i|Uz1(HhQ4EX>LORNemP=Qqil1fUdfK!2FSgb1OX7pBF?{XS*)0H7Dk9G4c zx}{`H4HDXfMo)~Hm;tvqW&sQxc0nw(#z2)VwEJ&1Dk%JaB-4?~YIw@(d%|fHHUi-a zrs^`?wKWy$KyFoWl{m$UNA{~Dsi=s1SX%MKVC|$+ETf&jW!`xjJ>+Gv9-e(p>&&CI zjgeV}byl`nMazjmn9OWgFelom)E0y^>%5S$&@@d#zJ=vo=+XPu_LH;?vQ-!jGUcye{9`I>bV>z?nrBG)zf53m)p#XBmZ-j5Rm z#^+nB_@eKe*s7tDY<}=v&wkJp5V%$Xn74PA=d*6{nX+vc@mKctY&E^u&Y7MqqoeI_ zYoiWdkcq{Ujp}+-8=zenwR-ieUcJnsCEo4PRb!8-NJ62oox@}4(m788?1EhtpwLI~ zTS&biVp`KJ5pmsYjE0Nd!k`kokdTVU^XyW+NyYmXiacGxzj zb2zNi=f!#dUjCf zIWPK`EHX(kxl3iJ14xaCO8jpGgnQnzs5D4|gKZ9pEbAh?=Vg}vAj{zxa80Hv;465D z3Cx{X*gMM;sCC>1+BLutPQCAoHMorfHJheua2JQ2`Piri_YRF=vyhI-@qX4AtYbr= z;U-`%!0Q~0!4%Y>j`t0m6OTa?pI?B<{P{)vO5g%qMEZ>461;?zO}7%@GTvXo>tlz9 z?|*~oAE#da1z!2X;k}B#=MlkDy@tOQ*{*&aDE?!bEFjN0WJ~=Pemjo(b_wc*Qq*~9 z6Q(AEHu^!XH;db;OU*EE(bj z6x<5@30~#Ot&TbipXSQ_tW>$<`R+e2Q7$Pl**M(7HDl1u@fPrT8Lvrn(l6mFxPv^G F{sGdB?jisH diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/DefaultBatchResponseResult.class deleted file mode 100644 index f0a5db8ea2923edd7ae24ea5030bbff0ca43e47a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11146 zcmeHNTXWmS6+SB|2Fcp7<0Nh4I0>S-Eu}hSbrVi zwCfM}o*vr4vUu3ye%Ez;j(@yTXok@>iD~OjtG>Cr&kel2#OTSnNcPcUG;O;rM%Ojh zYt?;w@9utGb6YL$Eiig_D|7;@!`GZc%eQuIzT`M=pa&MXWOP$IVQxF@c0KOXN2eet`GoZL8(zLFl35 z3;D94O=7T;*e0f3GQY40Qm@A>tn1w_SO&uyZt>x+_-|+r!MzhamW#El`2Dmqx6C(4$0m*wjBKW)+ixNUSIW{sGJVY zetgR0tP~rh?0RaH$@oeH+x2Ml=n7{BjP6ynZCGHN?})72Ov~59&@n`SfkB+4Cs8N^ z7IqrZx?;fwzGBhxKry_yzg)!Rfl;lht+%^b{2q6vcexHPh9(of54T!Y-(M6sTk)aT4$BNj$Rm9HxRy4rr2V|Yod-=i!?i9scPz4pjtlCA=C1pmiBSvFu7x( zG9fc|Q8L3fQ>_^yds6o-oBNUPdO1`~4#I+gu5KLYEw0X@Vp2oB6%3)?T0_0HmegBo zvff&Y>aDe1QDU7{O_%$ssG)Skvn(C(qb`P<28BY`L`kuW-suK++BLf_a(>@+;hJf_JzG}wJvrLHP=FMzWF0U;a1`6sLKjgy$##naZ z9wJE>+KJMEzk(8d;DtuudIjn6Bh)RkstlDfwgxgmBDxu$QdWOR=X499BRL@3Vq+qw zjet2S&?cuzIy91*Ku-Jf2GHd+K(R_mp?MckQx5C-^^gKfGd}z29%o58$_0kD*6G@o zVFjumV%GI%V!q%CWHC(q`o4#5$wRa>UPJT(OV*L)*f(qi3{_RcvEGmZE@c zM1@(#U_xS{!Q8<{Mge*Me~>Une;7N6KVO10d=VBHyZXPG99-Ot&<&Y59@S5R&w6h& zv_xBMX0|{^^2$q_X`?j})qdrrUwP@?8G!$CFOB-rwi|i|e~8inqYs8#V3~U}QZyO+ zqcHW3HY6?(MJrz%OENZa`T{%cx=fw0)4t}MxY5TwH>}XFade|dxlp0sFnVJkG*jry z)2~GlSRow;w$iOd>02QK8*M_(6%wT{IBJD(+UU)6JQhcA3R#S1(;u<}HH8jPht4a# zD%1fzY%VI~;$&5_1){ zU&dLuOZdNx|Lb&xUdR1a+)vRpJimdf_xvXA-=eqi?OW*eZTb#SqL$xHP~O4U8@R4a zp__P!e&5C2JV;OBzw!;;e184?uSxwgo~Gy)egy+WAJg~b8`10g^aHey;Hwh8g0=#2 z4wt~Y+lM!wfp<^hU6$Atx($3muSWHR@gEH^uCNr#{UkogAn`PV1fx&ETyJiEJixq| zpAls4Wsun(LguFlnV*eI=6(j57emN25;8xZ1Q|Yr%wj_3vq_NYWRM~H1uZ3lwJga9 z;af=w)imxSA$^cBM~4}xj2_Y>h*nRR(jqDZJ3@P$f%f|hG{S+SCkc;F6I7w0J;9$z zP_+zHmVsJ=#%?62o5=)!4x}EkP|=go|1tylJ&7#Tc!sv|J4MX|=^37Pa6QLG6ySOv a*5p_8f__WCqg^s-kJ_|PHaXM<9q-@fAs0aa diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchChangeSet.class deleted file mode 100644 index 6abfd94eb083b6046066c70fe1ce42306a5bb03d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1367 zcmcgsOK%e~5FV!`FA9Co@+wfP;?PED_rj@(DnZqXN}(VUia0vEZsOK!FSfU8e;E=; zaOX!M#!gDNGzYkFuy<$tdA{*{WB>U1B1lB{9Tk=el?sCy$#Yyf;)bgP8IFw3 z(Ee25T1ultGHdCPklg-HmXss0PatQ>2^(^{jwg>$7EB0biBOj4w=7YRB^t{T1zFZ< zs=1~9s$@DGNgXEvnp}dxc6u=UmUN6u1d>xHSj2VV*#>GilV7jqYV@_M8!F*!HbE z-xZ1<6xo2AKErM#u+-HFlZVU*Z%>?yEKM)i_d~%5v^Kial5204n8rQaCeY}Kp<=Eu z$nb3X#!jo%+HP<5%R&3Nb@g4LoXGgTIu%x6PA~k02qIYw+?`Y#kx&0bzGa4G+;MYd zOS7j7lkh#f69m@EbXV_xXet8FJ7q|J>-zDwue)mSn80#*<3;%h%)J)Yq5yro@3u++ zpAme`04gZl4XC1BL#s9MHorps<5Kf8%zY1-1)Mzwa4ZIof2Nk;4&s-Cy#jY9n0xrW bkE7;EyfFh0aIV0^33L^npj}1JBbfOG{o$%Q diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperBatchEndChangeSet.class deleted file mode 100644 index 395898d1663aa943eedf71a28156600c56b69750..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1111 zcmcgrO>Yx15FNLnNtQs`((+XfR!&I|-V3L;ssvRlDup7EPzi3%u9LX++KcV2+FynQ z65RPQ`~<|<2{cl;aN)4o%y{u|7b5cn8V8w@TKYo*tKHr?f!Yz`*P%(EeOrBOt+5TbOURFR+6rgXmB=a1S4zdb zG@6#yjFc$c9!;9Vsi6L*6f^*^(2mX8(TrIRR-!U~;X zhp{yU#vdy@%PGqgcaDyw67GL?$ptnJ1C%_wq@D= zXhOZAJSvzZ(13d_xDRatYX@_qY~Lpszc@$KOx`_-w>XxU!q@0Keag@isi1*F3`aDm;$a OC-c*%u!C_ebo~WutWxIy diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatch.class deleted file mode 100644 index 4f31ff44bf8a89a8ac5785f70b909f5118f6f121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2890 zcmc&$QEwYX5T0#AE-|KQLrY6a=@JUHU3ynuV=EG^5>er{sCFwM5YqZ?;%s}jYxee{ z~Z7aPr&3>F>{`3T-;u=0k_ zmND8a!3S*Nw-?G_sq;O|I3+)lxv)|zJ~DccV^MDK+H!vvxjh&mcMf8gP&`hEr$Q-h z`2hK$lbAoYT$Ws_Od85LKOoZ%8dNDo*fF}G^)D6KG9IV7C`-O0b1cs@N`@5J5g3W| zwHP8_r_5uLod}WaG07fJWskk=@mTiQ%f8MtjU{)P7fx+%sX(|tqI5&2By{XLEB0mk zpCQKeki|$F>+8NB`&y6HIa&83_(`l8^1LJ1Q>|2$=Q9X#tGV#qRa-twRCqFVW`iaiuj!fsNb{-TS9(;!f?|g!brECY+f4RP^w3}G_0_Pmch@BX7|iSuOIST zDx3OmGg$7)p%S(-L}{(j?1vpV#q|kz^LeGLEby_~lcgle-?$;_Tcjj6-N`W^Bl~Xr{NDGmxEDtF_raO3rb*bDY!n!iWMbnj_@uL};1| zgXCC*u-j*IdT`1c#BCO}#<5p?KhO5wngyMg^1Wl=!(N^3`u-gBJnyxhu1t#C^lf8s zD-aC-{mrvcaCvng=>bl~Sx;GMZZf_&kbe>q`!7aq{MPE~18 K9lnAXF8&9T?2&;0 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/FluentHelperServiceBatchExecute.class deleted file mode 100644 index 0f555405308cecc999b9a320607157fd6a7bee72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1273 zcmcgsTTc`*6h2*%y?`vDc*BQ=@Blu{ybCe$0+D1z14v@@rQ10`q0^3SX9K^?L=%1Y zM;TAMt1OC9W8%YH&ZXz_edqM^m-in5U>)u@p~2vdcQP-9GWn4hh+Nt-_oB+Bwsn#F z;y5ov5Tz|p=QjT4ho~!bd0zy1m~ZGBO?aAAJLm>V;-hF-KS5asY{CSC*%Vj{bC5sp zAE68kT5D1(qrw9QmzG!d7)(5-3{7Y==$?76J7--BrdR`C8i(HM0T!Gptdtf(S;H%5 z`x;AspLg23hseWeg**nxV!CXYj!R*T4ZM&1Tab)z2Ch6;rcjP5(x+tFM~6mIhJ9yC zlHb;3D|jZg@Sg80jsAaTDUcG!0xBVo#R2j~>O3QEs377dBW|+q++>NHY{*TPxLM?d zMbBgBWu$}$0tinkYIi&+(Vs>7vjp2;rWoR(%F4Qsa6ics)ave+SI`hLnCn?7^qz1k$|L0r4mV+&#);}+@Ors7R?zB7@1OQBE!#Hw${LTW3>LO) z6I6+tW}v*HG9Jd!PrZ8v?cNAi@?84S^M!Dt#DMPHkhOPGzjp^FiCQgq&}p`?o&;-~c{%AV=UttHAG2F8rso zPyIl4d`-K45XoNX>+sCikJ?mFKct3sWXK|4+P^*xY1c62?=`hiG%))#Vj`q!FMjSo zp1|tBWJE>FKWaW@fg!L|<${}C0@uo0Cj|2QXwrcqft9iBfl^Wx;09UxPZoq4e{H=sgD<2IQ)G~&56<8tkC5psp` zL>Kt#lqn_x^l++V2iHIK@LlM7K}5B7PkF@jf9WM;=-39(rNJ3(F?SO)_pl}y5Z1)Q zns{+dyirZOK~21*CYx?3nRadN9cqHMYY-xK-o=E|X0h;|Exb1X?#fPLOn9d&m5H(E zat`FPVm-s7EQ06Wpnh@1XKOiQ_$u-?+m@8Ah%Jib`AcHy=Ld;RpG@1F_pXsH9SlaP zcqoPFMe{&UOYKY1X9`>7((y@4XR~>16c?@M5MDON2nt0aSrFe5!lHP%NGuy7Uuh}6 zQyi&tf!D)q!PSlq&I8sR(BkZnq$_r{`ep(!D>B(q$qwKXM#F^wW=F=>q#@6kxU?jF zs<`cxQZWlIB`CnnWw;J65LhqQ$2(iBT(LXfN}UUK*y}WzYFNtz*6K2#(MjSdd6+kC zt_gf!e>U56g}~iOw@509>mQ$y&uXJl*+s{CXHCBLDHb{LNCgvu}v8-GK_ z*-YgTGiqgPP9EE3gk$DKb+&0NWTpC$6>xF=XG&mL$BCvr4dPJ2Obc( zks1!{ya@|79#pxLwv;enr2u6@su@mYS(zcz&NNQ%(gC$6K-}oE5lt%j1qP@3Zh>?b zZoo6ILiQry#-PVkh8-|wiKK1>ZcU0{VG8grAv^yf{@DQg-!L;y!m;eBfIY@zJ%Rg| ze7y{g#01LK#?EYAcI$T1latqwRn}_)=}dENm@r>*rl<IU%lK>c zU%}sdcsV3qpx}LYbtL%Oe8JaI17`p>Fw#x@pU2f3iS36Yl{XXHH*<+20##aFyeUT+Cw7Uh{bAot#5JU-6wLw^~=_E%X3^g1Yk$Xl2|7 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/AbstractJacksonCalendarDeserializer.class deleted file mode 100644 index cbf4159dd040aad2322eee6d7e194e7e7300068b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4443 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeDeserializer.class deleted file mode 100644 index 1172440c4a496eba3d271a39fc649a46a98d758a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3074 zcmdT`-)|f>5FUqX@3JJMga&BZf)gmcS(1Vjl>i3?K*dEckRRW zMh$-!FGwK4JO2!CNIZZTdvDuY-G)m=q3Xl!+I!>AGxL3(@vndV_%i_9f=`>UK;Rpr z<0xZ7Mi1CAi(-{UhK-{*Re2Jb5ukizZ|c&XhvB+%GJUz+d=ft4rNx3pHe1s6!;kNZ+{qtauZ zPz-4-Qf6@*I@W5C^2~fjgT;dpr}kvbsRIa|b7A2ejhU3n(gCL>NJx8@3PXiVghv0< z6a4FlYc6Bl;Yh0t=ilb|UqZt;Wya7Wk#h5Y?vila*f}spO4yLoO^iH5GSML)QshKrW2J48dyAT> zN&2i|Zbf<)!$+ZKu;6@_%?j|u|5IT{f@`MZ(Fs*YJ|SK+^JHI$B}Op|W4W}!V&#O? z>_)Ak%nPz`&#kpOkFC=5<^YE*wv%0VS;}R?^z=E~uHf&qcby2gKi}yTLNyNF(U}Qj zgd7&i_eM|W+8rfjp4PE29l+gVuC++&Se-U2<2t}&hzB1RVM&>jn@XH>Y|BK=F?;SY z?OW|QnN{j^vd$Jv0L!qt1OdE8V6EMMQXGptx~_Px^o8Vi@@&BM0mFJoVAW^!kYUxv z`Q&56j)Wn=`u}l@=n}Xxc6iZ82@5_q7SsZIF-;+7W3<2-krRg&;b zu8>bU9qtIz?TJSD`-(G~&KEoqSAlO&Aj`u?UvvswPq6&;>dFuB>M!2z zB0k+Jhp>jvB|i?}b$A134R{kS;mUO!FTiEEg6{xFSMmK8{$`Qiu0dX}Mc$Y~-aG^H hJ2l7~waC}Jx9?57z7B1i1AKzN03+(a2k;R@e*^y<)NlX* diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalDateTimeSerializer.class deleted file mode 100644 index 862404558e2dd78ece8ee7b7cce46ce5b9d454e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3066 zcmcguU2hvj6ulFi+HoDyh6LJ_!Xz!=@UaV3q)IqMRA~{eOe-~Mq&^@t-ksRf?#`H* z*{JD%;ddZ`1n>MA-grdZ+4Uw~yotBv~@;8q>x2z+aF z;Kxiz|0z3SexPFCu#q1`DhYiPKJ(4ikV)o;%(7U8Jo1(M^jXM8mTUh$3!WJzdn#bj zU1s^di1{AZf<@v5*LA27xRfGCOb+~q{ii&zIAv0k1Fnrw@^SYr0dk+f;;xd$GHDN3 zl<;}@;)mAtFPComt(9)TB{EMQOU;dnj(A8h zp^=D~MK^S$RX^gf`J6Tv_J^F>;}NG0z;nTQS#UI9QYuUPoaP`N?OG}f6*3eWBxdO^gDp@$;k=Ku5(a&+th7@;6+!085N?xBiO6iTI+@PW2hPvEiW`iNMND+TN!h|Pbv0gk^FJ4OAOFPLp5%EZAn{jWkVoFG# zHcYIDsu(`^QUx>3r`0R~kK;cRb||=JIv5^Pb(j*8*39`upOs30r7Vo)(l!?gyQ9Px zwTiN|9JAouTC0=5DoyY7@skC1>~ohzT!u`a?y>Cx`c8Y-72w|{H?x8@&XJQf7RCrU z$aX-w;N^mRc*M09VVQ1|xjax(CQ+GalU8|#`?wvk0U$IsmKiJ+a-LzEQ#Qk_TCf!} zf0dl}tah4ASL`HN)skt#A}qI{32zWsZTC)_Wagven&?tbNd6#+`&{oc?281JQ&tZc zc5fVyCu?>n41w=@|LH-|A+R#w_C)?}dJY5_)YbN8uar$(valxxlG#L~-PT!l+r2`u zbb2|3ogIX@_9&6A19jz*Fap!Qm?o!{QLSUL@$1pcUS-sG!i$Xl%l zOZk|ATp(eFZO+j7W&}1+vaF)=6(s`RK3kZnx}4Oe0XGT!UV++boK+OyRjBk!2Hv_nJ0bD8B-{qbmy#gr8}pxGw$pBoUFr#*m82fLQAj1 z#{?RArPRU03ts`Y4xbioiy5y3THj>Hl|wVI4tNd38!Uk}n1>7auH$G98aP|SmUj_n zS~#d>c=Z*wJ8anv6`8^-{j;jf7qZNf+J H3HW~jJ!#Kn diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonLocalTimeDeserializer.class deleted file mode 100644 index e2405d38d41c730ed356191a60b70728702fd617..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3026 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/JacksonZonedDateTimeSerializer.class deleted file mode 100644 index e5c677ac2ab01110918f208ea55acba4154f851f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3066 zcmcguU2hvj6ulFi+A#?!Apx53F-a*neC$FM2?rK2Zlm~lvX78N2_nbTT&ToJJ_%i_9fU9+w zA#mU5z>k@b{zG=e{6NLNVM9NNR1*3oeB_(00h7!RnPssGdE_hi>a&myE!X}{7CbUa z-cyo?*O}$}BIbKs3l@oQxUNHuz{M0fVzTew?mgsz#VM1T9B^%fl6O1T36Prv7Iu|1 zmPvcSqJ+=>zV_{}e_Z|Udjc1>g%oy&zzeO-0|K>O^rQ|i5m-FMzM-|!4Y){V$z7?r zQPB|(DF!qY5wo}r9ctB!cx*nS&AI&nr}lWrsRQtwb6(~g4VaY5(jKQdh(|k?3PXhq zg+~9QWBhc;HJ1VIaHv&`@l;!fLf%8KOSPh>jOEfc z=L)BzWG`wJWobHQ&bhT#CxKO(UhCm23+!mmT^4Z}GJSf+whQPxtzDOZPfTuR32WR& zPS#i$BV<220qKILbMp2P*II;Sx{b$jOG%kTWulE+9y<78C}rU?tMG!IR9g}_Ryds-y3J=(5_E_Q|Fx01NW^*+P8 zNMNa}0u~)Gtll^tP1fu{7y=Kv&$Uss2`u-yJ(0hY_JIJMy42e2ma>UU=JrHiGMi|$ z+d9i`yIV+>b~mT6y@L>IcM|C;P)8mKBQWf1uKK671EXFZqb^t8?b{tCOS=J-z+V;m zO}=`Hyw#Ghl#eOM1rny%<_w*0MqmRa%Q7loQ6k`{XA3itm*d(r;2i>gR-m>TXB7o_ z7Ha>)gG->frxG3T&+!65;PUyMChfrl%)dRGdC+I~m?zGVJ2@*I>H1XGfqspuaK2>__%Oe%y=a*|7F&$9GZc3z-u7hUyccn19%r_2Oz_$K2^PLuTKoZC{w1|r#(%fUA*|s4d}al&!fQCH!Rv4dSFYmw z46MN$_-x|aoA`VSf0M{>mmsf}BCn4jZ=4`IREIu^e7OX9y%hNhuAhN-M_#`NEgS=U Pg1;t4v|eR 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 yqz`ujG%)~rvj*s94bUVkx3^ zL!wgqogal5@9td=a_A0$0I46=>zDC7GxN;Ye)IPAzW}fTcRZ*O*fcs0GbUvCg7sM# zt1L7u593tzlF%f(p}E^-l7$JgEK>L z#)z<^Rfgl2JzPs@5U0!-x+79(B25%G)cKw<8+WP25b?bn9UfJSqz8`lpa|S2gOo85S+2A#V=vU0kRKA;3kq6F zA`esaS@O<_j#8bJPqO4&NMe#!)iK68Y87QgBKys%lqN`R^tsj|8RzP#vUMe8FHI*X zTQf>N(*mn`g5WypVpx0=#HMp$bz@~O=6O-mhf`tHi!bWzbjD2|xJ}NIiGiEmSI0$L z?Q;3i826bLj)cjnGL-b80mo-y22K!ISc(o#+byew?6llzJQoSc*L&GE*KMbsz!<;an0iGuCXu(oGFBVT;{9?Ay)1P0)g2-gfVy+xk&?80AGptN(F53Sb(G0dN{gUi~;^utVS? zwgV_O!#xJ5AS510yju6G_xa z%1eIPfDPEb?{DmX3_D7Wtu_|yVqLZml6cg^yN5^e?)b-Fzx)OO4fwhOB?1RZdQM1r zmFrA4^IS>V^R z{tA={+(=ahH0n9+!(--Y0_C0(5rKJk7r&b*OGbSH*1G!`J31&Zxw+xKI1rJw#;M0* z%|)ataFxK-W_#2_;7%urG!I!b8gRuA16GeBp=k;ya5JxPz@+3pGb-Iy5(E^{D7^ck zhVQ7PA;UR7r>g3N*BU1g){B1x|H;624xw$_SWOZ_I{z= zF}*z+7oswD-C2Ax(-a<2)xSi0HCTo>7vUD%#>n`9M_fN7u(F;DTG`Y#Hx4k=xIALn z&fPc1g}|~aJQ^GfkI%5PtovLM_}=|@;yXsaxt}E0Xnr+|-A|1qH*=&jS0m{KSS4iX z6-L7>0#DfvCW2+8rT|}MaGg0C5~!|cNzg3n-JILHNgQJiP*16h@Qf?t>H!Usbfp6k z9*PrZmqkosr1Liy-~&?ra|Yk;_gR`uER(8ila%ekvtV0IZE4-@d zd6>(NqzF;|F~NVnWqSct$~xi!Q?=^lS@9VfHUwhoozNb$)-cD~Si2x0tX*uZUF@uO zu}8Iwoz^b)uy$Fqe8H4u(k><%uWxB|5j%}>!g;lLfNWDB*{S1R+DPUT9>zlI%=cVO z*pL@tiRQuo9C&J{RZCXZT&!R&%;{zD#?d_?dI6i}g#Q+qok+VgJP&o*Q`=ahYMFRi z$cfy|Mw;-$$`yQ>)+r!UL;rKg?9B57_pvS=*7{Q@6h@i6y6IEFa=j}O>9HM*G#D`p zE*|MA#o*fIy@={Wngr$M$UewL+F^ovh!OU-Ti^!#x6+I}Tv05KSfn3u47}3opH;xY z8-U?mfzOcKxL~fp7ehHqKNa{AuPZqY1Qz$WQW)G#r-D@=a0TyVn95-e=CO4J+Z9-V zGE`A2qf~=MY}KFy*YLZ9&vkq%1b^MY#%uW8$6+P>)_#TMXUjK#hBtmksRVD~dmeEB z@4y{=FQ)Ap+=aJMBG`NHf+2%9hK!MGB@~PilG;5tWf*6stO=%$Cz`qiYe>k<#29!F zzvtS<#NVHw4KH|zeV+IntYcpZHsHhb`_V}6$0&b-55QOWETJtMJ@+YmZd~*?lCQtm diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeOffsetStringCalendarConverter.class deleted file mode 100644 index 6793dcef2120067e5d7a2068c75c303d18a49c0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6761 zcmeHM-E-VD5MQN9opV#trVStcprW)ipScZEJj1Y)Z75W_( z3;zk*W_~C;e#*Ljn8;q_r_q+5&TTTm{D^7Rkr7XPX&!wRv99LIU%QQOc=2Z3;cKl{ z%JsTdv1l)`go}u&B`LPKLQ)4t37pELBuupZwT&k{)C7(tvQ1zrkgDycNo($jAINr_ zt9b(FANGVszAIuoPUDS)FA50;5Z4wF9RK zOb)~?D<#!9oFONEeI!t>OtyJM(RtlC$<(7=B~jl_dXu`1<|e25X_r&Oz%#~sx$!h) z#vr=EX~D#!E1JeBjYSkI^iT2>Kef2xB18!-B|G^3OAo(A)C&`qrnD6&JUvV)5l6vB zfi4TTSew(!Xt{^5L4^=D9>T`UVdFi6jhDg3>%->=?UQ&hBvai=Luc|oWHG&EkL_>2v4+u#&>VNY3M#kp@-=ZAmJ9zJGE7al zSN|wQ6(UhUzYrLXLpvHf=)IKSuv$nv{hs5;1BK%@Q!&Fz!(stTq%UYGhWg`0r?9Nf z*}oZ~Ld?YYUHALU6awZc9^;nXUCuJZGn)3#VXu0#3r~1g2-o$AT>L&YL=Z zGKdAww}GY!nC!?AvgC0;fck@@dNane#a3`==OR3EOlq0?6)~`i8$W@oRaOLhQYg-B ztjBG^bWfo@H*DrC1cgiIzc%xOKJ4)MY{_v2%y9V1-b2^}rWhidtI}y~9Ns0zZdsg# zUM$LM^H@3f3&Sh6*fN=&hF0n3RcV8H!fm`NZ6d4EhQMiaz~7S!F3t=>cD5n~VAvPP zp5OOuk9M$H#g|>E7T&3e`&zP6{nYPm4dqxOtxZu@&=`J(a0xj5 zT86-xnc32UFPS0$Gx)XrqeF1NJ92L76v zfY+P&JCRetTgbo;LIQtfyKdRvTsDkqte2nfK mflvBc&t<&LihYXXpWy{?7q2N8htJ_ln1`?67A(RNEdK}QwV0Lw diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverter.class deleted file mode 100644 index 7d0e523b4c480004f0d03a04dc8b4e037288c63f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6091 zcmeHLTT|RN6h7KQ%(_q#lJt^Gqtc{-CbgS}^b#m3<&w~GAQNbMdFcpSEC#Q%m9-4~ zmHv^=v@`8{e^jSO_L^P5V5KDt4}I{iBp;p2r*pIZ`un#(0AL=TI8Y-HrYiKCEEfJI z+h%?!n|{h#ewfI11j%dI0`ljv{<;s8k~-z zp~HB&!+3iR<7E!xbsT1tMv|x0*xqD1+@KmE;yW!gxR)(@rDwd-%Yb`n zAvq>-vn7?zv2W`*DFOW~5$<@IPvl;7*gy8E+CiksB1^J7Ogz400-=;AhG@w;)snKz z!QHw!s0xx?Oa)Q7B)7g|5=6z`nYC0a7V1?DxxMUDu_w=26+}f!t03#7l7CI*)|aFZ z?WD|8d$C@|ysF}eR|qN~h>pxTy-Y1_wA0WE)u$!_a8+T29jMi|y7fScGA~Bd+s!qu z7No+IG`|z_mZ>upYUQP>g2+ZIOWcnk!_0uC@<%Dk9Kx;JC}DT1uf*@HT*XemV=&t1 z>7B;p=(srPK+LN3;gsI1lobr7pJ;NuAtT!0S(+Ogr*b{-F>G2f+H9wq$|8SAMX^X? zx59zz2M6*F?yRrG4Z(CpM%if;?vMq- zJwm=ei>FX1S>dz$&!KDjnP_zgxGS=)LcW0aRRqRu8rAILhrpen^qYPv=;W|&#xwna z6J)mpIJ&$jLcA)BQ;nVNlNfLRRJgJ0z~}g?l%oR=2@DiyJ1~WP&VdHT0lXS?I-jEs ze1#W=xfTw5jnv&c!Yl_kFt_j0vmjl-)q@wFcq{odPE)+TG!c{n1-ygAOWGP-fqpoL zGY4np-^Kqc@H!0Qe?LA$@CL39<7fhR5d3$4hx0#-jQk3h{=``g-ojtA6YzN(e+P3a zcnA5IwX1Lq`rtZ_Yw&I+$HmbNoKt+>!^a^=W)=!DH5}(+MoYw8Diw1RZtaQr;AO?! zE)jFLRLnShh&bfd+&LtsFrbUm-4f0ZN;!KyoP7(;bNSiVwhJd`4gZK7%h{6262-FaxtN{}0Wzr?3D3 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataField.class deleted file mode 100644 index 318f970de11673cc000b9865a4c3d6b5825d6dab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2331 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter$ODataV2Metadata.class deleted file mode 100644 index 58691163b096b2d83f12011bde735d94130b72c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6771 zcmeHLTT>iG6h5;+7FGfj5HApQf~eqSMk8JlVib`ilnYowu+*n!r`a8to!)f!QsAHR z(JHOdcYl=S*E73e2m{l|#2YdqO~0~S7IINqouTW zHtoH+#x>+LD^tHiW61;&i|hWz&K|F6hx*zwbZESKJhG`}7}pman*OE@#3o;j4?-30 zM0_cZg=RVwv2v(VU2jJbLpMans+(MfEDC?*!8&W2u7Lv^7-MhQ7?OA64^RJYyAP?sdP#teJz z4tF1aqlL>{qkXT%jIsq2K6@f23eGpWLX-5;Fip`k+~rOfhx)EV*XFX0k^27pHdeeE z#(cfq+~IP|c)4*AQDf0IgQKI}>Fn2y5Ye#P6E80cVz^`(ZoUG)AEX(FZk+Fc@W1qf ztu#VZCqibQ4D7F&L_{H%oQUu;n6N`?nwwumhZbo@a%d_CM8q-D5HN1#tYb!( zPF6eJGrv7ZA2_3x^QlZ?d8@&baCJ@acqWZ{spUCt=eeue4V4?lK`4=Fqa(!qE|)y6 zAzSQ9(M0=E8)Jd%)gq>pyBkJaRfhh#SptrUjRGxJ+h=v|&LE38Aq@NZHg_W3D%YVC|waq;jJoJvk)`q2v$Qx&<3sH}F5%T|%&l*d}G&YOgJF((y zkZuCKr6^jGb+wG0u9oeZ7P3gu&WC4gLQW*6j_A7hdsFa3B;GhzpA?D@b`cWW>{m_{OHml8gx(g^kyRSqZZ5IbxPReypHuO({ zi_WR6fDAjz?A5bcr>%zMEJ(J|1?sKkriScN*(^daO1dp9P$oHi7Ra1$T4iW8;K}j) z0k*frYT2!homBB`JtM(Rq28>V&7$B4x6cDR$}Q5ORaMCPs0F4?705z%PfZ2xc=9@? zNH{t576|Go1!gi{2q>_ID6;bGA0K) zT&7gbz6Xc!$?EACLH9)Ul$)b>cs0gVTapd9D$^Z@wt8_@_~a!Du5wP@g?@J8E;P-5 zP-rbw3ipAg>fwrGaGfjtKqyTvffL-=_ECuj=n7h8v_{by#Q$?>4=W6m=;~R6S1~wp z*5C*Y17eiMAY_0m9pG`iPvBX=#E$9zl_tL%fBt8B@prUJbREA&8KNKPW&9cx^YttA zD*hd)ZHIWn8u7KBBhJz^tTR>{ON_;@<0(667;d}fm++pNZ?EhdPY?Rdrw2Vp^BqEr z8@vU(PmuEVrAX}D$7&9)H{q0WEkN(vCZsx>_lN)_gh}$%tyG(b{j;XLk8=w!NL5V&}BE^vLG1{Nt exrOI8o(lAKXpug}+vjLsfpj0;r6s(VXz?#LZ^?}S diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataVdmEntityAdapter.class deleted file mode 100644 index 2db9f94abd808a86b9a28b7c03db0bc8aa980618..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7834 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 Z0JpIZ@EQK*(G@;K{6B_Ip#gVb;$J}R&?f)@ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ZonedDateTimeAdapter.class deleted file mode 100644 index af5e0da807b0e4c2fccb4225f45288d5ebdc7165..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3857 zcmd5*wAwAzEqM2yUcP=M7);1EWsjy zTZz7q$s@PjJLbM6@MO24!p(z zfs^JnJ!_*^Z0uRP-#6TLtQPX9&O$B&rt3mOCAdjo^*oymt(9JeRkF17LTYYQc*+Bczzs#ntWc5;wd#dD zGC$Bt*&A?b&xV}F3Y-`YCxwIhOiE>GkJGUdhwfV{3>7jE8W9W6@Kc{_E`7An*DAvK zFC+XGP{$9MF|;p2ZvIOv0Y}5J14HJYup>@4FmeYeiw+@W9i*(2Qr3xino!nBDC=aD z-JpTuhQ_f+%=!arF-82<5Cfi6i|ljax^R-jUD-&Q6A=xSwrTDy7Dz$TFA9#V2*6aVg{LN7D*^Vwzz);(9{U zGEhRq9NY6-EP-5ZW>9=SH|Hvf&-d{BN#JhIpRC^`2R8!Z zy1neOGsYBaVn(d*OYCsjLFd`{h*W~FumMk{p#%oKr@J;TwDFuwc%5z~1{=MDT)h6>)_#o5zj4p8|WR(@W4?^n3>XR_)x-j`4Y z@VbNdm1JCjb@%{hi|`?Qg!j8RjxF5BX9Y(eO4^=rk>51Js5RZrvzSl@^G!O;HmffrQY+p6pI*&n)BF zsQCr_2z~^QNFc#GKMHZiUT5vBiFc)@$wMD(&)CQ3_MMyk&%b~C1pxQqjsrCUKgQbk zA||BwnC&yqSCJR9zUPN(5O{I$#EVyYOfoNEhD9pip{MN9V*%?MuDu_WuJ>^FG4~CDbE`rMvqs>h*76R4nRVpsK!d>Ci1xnL zO4s2PQvdZksd=ozeI8I$urESpgp#zc)o#e6_zrD0wtJkKgFdI0fop}isW9p@DV3qS zoMud1x?!k@sgQxts84u+kM_9c(nkqiQvzW4b3o9zRbh0Y|}Bfj;w} zur8-dXt|5fph5_Zi_o|!H0~iZZUT)vgl35bipSL29x>zhs6iL;r+qXy&ld4_E&gue zxT%GtJ`qu0X_NZiU?7w@eVlM$M0h%cPu(OY(kL2_kAkX0k5nmo9NFF7*l6aPZH&*u z)F@#{jiM}x;%1|eR~58b+EtJ}#GKS3C<7-ic~eO_2+NSkQ)PAp|C?n6>S1B1&~P%* zC*Yc|HSa1dm^{Xn%|^A&&BiG@c^bb==ILX+kw@2-ek;5J-D&izqCE{Tl14CIE%KhI zQaev;J66O^PX)BgliJW)rfM(kVg*JS3|ahX8VzDYRhG|6Q!K#P3Ar>_K;l6ko3HJK zDf{V)$+HdM34E4WR}no$cimIAXW4>!VosY(uO`&t{4Bf-uM${jbw=aMmeE3X+jhK} z>j=p=2hlFq+YBcb0`nc^vv7xD$-({b=8Wlyn7~h+6CKIgsE<*p|F8;dVyP#v)an!> zuD#sJf^Asf@wLX5=t^b=8VztOUs&yA4s5UC%U2!^q@4^lz$Znhcwv1D|0i6?Ass3#1!ON=eQWn0+AP z7)K_X0Wn+y{5gdGsQ^bSg2y>LJGfiHy9BSzKcW8peDimh{Tp{Rn8UNB0X#0?c{Zhj zi!h(uy9AeE1{QE#gV*47yf$(5243I9<1IWKg0I+3CZ>k#RLqqUF$<+)7U7*kF;}M* zLrcUgmWo+|>*$Bnnj2$cG6P0_dbfo0%~H`J*B1qa|zh}jeb z7nYy(`RU2uKc8O!FoDY<^bjUiJF3_#ZMkJ~u_EIgxe%L@to$H**swB{oVbdm1d82A ztQr$1AC+EZKZFy6tAj&eUDMlYD=s0Ny~n0@^6Y~koM!O8V-Ri@jV52oR9kO;73As9 z&Jaekg(S&9)JKGV%c1GcXG^ZvlJe?$sxGQ|>!=qmHP>#FL!2Yr9z*<#c(g|t&%K$~ f^>V)xyF)-I@;|yNpr3mW1~5A4i`)lU7cl+>=DAtU diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/NoSuchEntityFieldException.class deleted file mode 100644 index 8086e8d432bd0c7755e2b907098402a7818962ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2151 zcmc&#O>Y!87=FFku*ozK2n5=IgqzZmfHZT-sURc-OB=Keh=f#C)q{7&$(qdAgY8L^ z|HGLp5=e08!msGxKs=t^4M_-EK(sx0#viYr_rs4Lzkd7tB@wmhYKt0-{&Y4?3ZZoJ zNNkHFHAUh?nWVX?vczRuiCgLkEs{)lQJ75Ti3#0A?xeEx%IIXptX65St-bQQca+St z_He01O-3hc++66*O)?I&-R z=30!7T~}K98;oWbI`7d|y;XYi+TuE+OAjizFXXb`R!((ud0$vjNH6V8t&JBUPRA(f ztWbEXmYJ5 zx!)}%4*+pUG47|xQxW{~uH-|Im@j*-99KG17FEc1v9uwr)G0h{SW{s9xWZb-<1`n} z@eP$r_g{L+BpinTN|A1fP08nxc?>!rAkZNO9pXM6;ypUVH67wTI?VITNXJ9=h45*Q zdlX6Tlt_6$FjyA{*2Q({zQ9etE~+Sv^>zJs;=1PjQ~I#}2kdiGbj?=0Vs6?^SF_Gm zsWYB9%0tMH6x6M*JOm*TF;ul z!*v>A_la=-Lue6A)0ruX=o`pDM`^iI6UPt?G35-Ii36> zwX_0v7mLxVscb6mK$jR@{J8GdmzvS(&IgnS$n3ICp~lL2sr|1C;5O~;w&+K^3x_G+ zqRWgRvjdv7Xz{>%(kBd~so#`yNMH?G?C5)9G=>+B(HxD_G5lK?HE4pG=;Nr-pTZ0t z7Q8v=oIqQ`mInUO3!468=F~Gf{R*Q7&Egv%0^C`Ar|N!0=jhvdE~4+gF5KJz?s#Jm z-N6_`VxJ%5d4OKP&XCDP{D&Ebygm=DNgR6`DDn_*9^)U-h_0YbppKX5Ct7$1oPTiH diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class b/datamodel/odata-core-apache-httpclient5/bin/src/main/java/com/sap/cloud/sdk/s4hana/datamodel/odata/exception/ODataPayloadParsingFailedException.class deleted file mode 100644 index 2c9e96bd904ff41bb6f25c83aece80d6a18b0597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1616 zcmc&!OHWiW6h0lm%UlNsQB-^uBvCi_Zdi#iF+Ad!5r{kzmYv?h6nfih+nF%GN%tn2 z=-$15!JlC~y~7MXNKhBJ=;O4f-+A;q=kvGs9|2$m<|C*O;Lh?iXHusJtjkhv^3<^+ z<;rxj)MbaMTWB-Q(u{eQn@p(Ggl#I0xG20dI$d8zd4u(oVc7<=PU`(tCY8um1}YJZ z5|}LU71R6adh0-Nk4H{e$<&U(M@DZqmI;s-1SXb@cAja!!&FC%?RF=ZX5PI0Lg2y^ zsil8PfIe)Vply1KCKsDSbBm952+Y6gXfJco&|T?dO9^z1<-!a5Ol#v=aNQAzn`<4V z7~U@u7+t~uBbX#mKY?Gd*4S~FB9*V(+6re>S7a3PR7h2#(83ygpS#C2u5Psj^}RyS z0FZ=elYX?6vtXCD1Ra1RwBf0ARO(DxOo-~?)t<0IbF{E$O^)*G4&G%n;fgs&_b{*S zJgsB`4F?Ab#t+%PpmP{`f(${2kRb^&BOTXtot{!t_$edF?iF!j>`K57nK$SDp z`h+|~=?}5~gKII=;c5+Hm?kiNN}+}7syC$;Yn{9$>{ciq0#i-Hv6LAWG|I<^qrNS% z$VBrTnhe%&ylFa?i&ZQV0uRpaOC{4V$cETUo4 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalDateTimeCalendarConverterTest.class deleted file mode 100644 index 22bba1f5f3a77a1d644ced4c3b9c2c3320fb7a35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4564 zcmeHL&2G~`5S~qcl7`ThLitBp^+J2GCB%s$B$T#Nr4%F@DuK9c>`k(%y=&I%h`tc7 zfCLiUc@f?MG3z94L!2g&Qc$ZN;&`)PW@cyRn;HA(@2}qhU>hFgAxGeStUcH8*tjj$ zX0E3~H)fIR1uF5~*gtfAW>~0v9=OWhy3A*h;o7Yzj|E%I@Tv%TnFU<>OqZ2xbB)_o zl$eKE0?Vn`fXRmYy4K>JAu#`3NMT+Om|5RAATV1->3LWnuylgm)>`QTTqA{FZ>8q3 z3fkPKXk8=%W`vS7(yA8lFn&gh3)Lp4<|yLS3UI74r=yH|OiE>FjnkeGhwd6GVk)FB zG};p!;Z~h%EUaT*V_FvhkN>3>pQB=HK*YR5*5GsvPwrq`&>)P9 zgK=?=Hg*S5lt~-Ij^I-ubTP&-!wzZ`Who_!3q5WQ zg|(R$47oH0)ROE>*qNIht0qu)>7ji^bZCG&Uhl*9f_hwVNBp_m)Jm!y!2_(K12u6V zFcYEolr+r@shA5B$+@WLF*rEfMSW4taN=nukHU^YzGEfq2$2|Rd{NZaCfWn07o&3kEQuXI{NYvwDKCz}PhLuNjYGt*slwt=#I zftWX{Rt>FdJ#t-jw)5!K6ZKdECCl-u>M*0sZ^g0LgxX$D1DXs~A7r#N_-AeHblz!kU z2TORh7{JdmUP~Pm+<+Ba0o=sh9NfY?i(19+L20)$q%CJkyE}%odl}N6WJtr7i`L}e F;UA6i{5Svr diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/LocalTimeCalendarConverterTest.class deleted file mode 100644 index 871cd68e3367d4aae17492474aab246e51bbd524..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3403 zcmeHKO>Yx15FLlM`5-i(~CR(*49-Ak$Qf~D)wfhODj=*z^dBeqMz@$``wmCf@@#vPN!cZYYp)sClAE!E8 za~a?Y9j#&_ZhqoG5Ln(p?Yr!G!lG2>ly0 zTgGCgoG&o7BAUkNqU@(wanpb!6(+*A;v^CQa+oyX8VgYxtJA*F8jg^kr(Sn?V$1(xqldJO)DLFf|k6pjJ_qReGg-aOD0RHUr0iX~FQA zT>xnAHB&a6;|>JMJ!#4h8$GS0+7vuOqd8_9L+Qu#xG?A$Y9-g?#Em)ktdvi~%}@eI zNY0#qQ4={a$T9W*GqeIFxK@RESR}AgZyxro9jk@xuDiZgYvK{#PUALqLwAMAZML4ulaH+H=<=v$l$nEHqt3 zt(V|7DQwTud@sG~s5zW=j>bJa$DR48Tp0H`lWP*Fx>{_hVI`K}9wDF3r^Iq8PjrtX z?lb{+X>p9^9!sFIqf#C4O?>+$@F*AfSgks{$lfD?0>0Ga9}CE?^RR&9B90#8nBcwg z6-w`xE1#kI9Y+PI;niUPKTCM64pDF&mT?4d17{0x6Q3Mv1@Fh%ZcSjTO=P<>1KZsR PZ1o9jXrdTR0UrDUymVlb diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeAdapterTest.class deleted file mode 100644 index aa2b72a5d135b76890874aea9e5c5ae91c0e2a2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2567 zcmeHJTW`}a6h7{{waq$qW8B7&wWsYRdEtp6q=^lwsAFg=L-6J%Zs#tsgY8rse+&sE zc;`nUj+0$FwNr~UAtCXQ#Ew6I=YH&;zrKD4fL(Y}ff9kYMu&mr#s)psXF;fvz_2t3 zW0ggLiB5uuS(d1X$ANOgfJH2|Tn8VOZ7ILzf$!@UU?(xtP zn0YRwurCNqZEPM9D8EF`3e*V9UtxE(R=NtaWcvL(sku>cpGOpRN=3}9P?DxvwPT)` zXVj~;jybhwDW{IWcf$Na7!8?}%F;Hc7bHGySSkz^G7=i~iO+DU!!?&7O6X{n;QUjD zYZ3Lsm>EMmBIf3AN{KiMb_%2{JYii<*U@qxqk#%xG<=MPe;ExwkA{C9jddC+Zm2Ul zVKzLb7Jb7{Q?zuD4`c1SSo^uhCTB0aAd*yROO@{W=pU|aZ-k?-L31%e-6qUpwoSx0 z3DB9+%P=j+F*;XumFmW0W&arxgF8}mBS~WsqFyFTxyA~R#_BAzO4B06H#S{1XENd) zfpthbxPoJr#c0bF+NXP&v2F!Xlu4Hp1@Qm~LxwpqLJw*cWqFBtwZ{JLX~@&O2#3pS z5P4{|klitKqq@iKZ+UH$Jm4&HE*xqJgE~(PER36o(?LTkIku9Ug!h>i4Evc^yXKD@ z@vhsA#}@R^1goL6x46#o+Zdf%Y>VafXf^&dHA9^VcF_$=VNUZ7wR rmT(5JjH^>{7e{4WS%LdFa`*@M9p-&Ff_HfYZwXd$e+eGL+6D7(sw_yW diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/adapter/ZonedDateTimeCalendarConverterTest.class deleted file mode 100644 index e3ef8814419cf6504626616b06767b0b61b20c63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4968 zcmeHL%Wl&^6upzak`QPqlsD3-8`_P%K`bajLTM{iN_1U`X}K-`Iwrcs>6K`nwRi#VRl$+_3}?fL%e(-#2PfQNHXAg~u}&ow+Y?jdV2 z*HfVzv&i)VmH2M#AGtm=EL1)ZTxD-v=CjCf?d~bb{dHz|U4*>K0xo@~t4g-G#_c*X z&cQT+g*I)#X_v&ITZW353h4`t z`UJ>-4<$6T3UU2D!9AZkUclm*HblVVzbVD%DA+0xG4F^SaJr07?x0^#A@qxb zesNCx;25VL8O59A{{igQKVN89b>GEbu2DA5xN;$3K8^yTis8`0 z+0}f~dM{#_7;6Y1P_cEQuF@(|gnOj$?R?UI(y@DQA(M{XcdjuoEq9J&&b5{jDDS95 zdwdg*L0RvFL(;hr^g7$R=W^p}-tH-z|_+9=C#kY&)k5KuFs{+j9 z)nWiY3wW)hD7X%bxB|F=y9Kz3cNVpT--B$oGT0U~+3w_EyPLuGB!dlS0@S7e4}JsN CmyALH diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$RelatedEntity.class deleted file mode 100644 index 3805b44ccc5caf6ac6d5f2c79e21358da608f0a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7537 zcmeHMTXWk)6h2DRI!Q|@PD`OT)KVbfB5^6VCZv<*(xf;}X+k>iww5;bCbHJmt}+Sy zFlJx|-g#w)pTqF2)AgBVP3iN%mFCfH@Sw#bcP8RY^EJV$Y)jOc7BYMkNv!Cd zRbadQm4 zw75mDW!7c_rWBTjS42HzI*zcuvv+;6auVE4Oo5KGK385OS}7M-AIIRV$rr;dp+qg< z(_tueA|(a3D=Rz_ED%3&zsi~%+fQ$?AO;KPD-Tsyl2|xbS)PU1=LHXZ!)~Gkv&o{- zI@hUuvjV(0Hl7hfM}*h1lX0sRaiyNHD1?BG7|WGFHfwUzeZWH=8O>pGa0YrW(Hj=M z+EvP26v=3au2?kteF!Wv*y6qoyJ-n%96GdHkp#!hyY|SiA>HjIV)=iU@f%Zq|6T%5a9ETY4;5V-P3a+mnO3 zAt2`UlhM9KB`9JwvCz>T;qTh$&--Uj%rb}QJ&SJUY6_MICT6oXd|vID7}7E|CyOp- z^s~Ihqe%G1imxQo+is%p#2DPF48u4GCWgqd%GJZ^P{w3d8$7WL+nAhV!iAHDi#~Sb z*o)d0$`+w7B1Dj22PU@8BOZFN$@NG!@%}W%nr}Ovi5T{}2)OzmPf2Xz*je#8xHDX~ zn77I5+`a{ebP&kFH3GSVK<;z`xs${YCk^DcY+rI^8wYJN?KNx-`Sa}-q?tvBIO>=z z;Uroqj64;zXtrdeO^k5rLO0@CAO2rLzF<<4QQd(nW2sq+Y+iFw6Z1bH~04dAxZ z4PAK@BwWGY;4k|#T#q>)fqw(K5hDv0!C45sN}YE*X~?ba~fh!PeaV4E_3B#Hu^ro zN0O@{nCe*bB#)UpSj`-iT)`rAdyE%NripJADNOUwS2nCao!VWQnzH43a)UFy6+4e4 z6H8O4LtkaHgQQ?Kd>L^LX=@#0LGeOFoI>u^vDcii)3wRN0WCT;X4-;h(BnaYyQgoM zy2ix#j~3nD+m3z2+D_Hem%ZAjeTCkY=`5mu^|?jg9p1k(?W-fENsES1J0+X&*l5@1 z31f>EjjYHx^7uS@R$wB}olV7Jk*ai(;)yjGp@NF3QP2XsSlJ&JFg;q?S8pV<`^1IW zVRUqZ6;t+G^h(kXZ`TA^yR;aZx*c(vhk8MvRULVa9kj_8L-ef^SK+Eea~7>Vo7O)_ z>WSnfS^&izk(cOUPq#dws6>^X&mXC?faoQm6dLfRcMR(tsv;gQNTul}!2gPVNENx;ie7?PK}`oEnQUR%JLg+7Q50i|z6P@c zbhmBs6NyyHG)R+}{{~|U;~vI+45Aqf<8$~DM)TOSl)Mkp16qVczojbP2Wa+R`u#w% diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntity.class deleted file mode 100644 index a4e91df0b6540584074c38a97214933e9a499eac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9357 zcmeHM>r>>$5pNO9vKw--xw|}kPOQ7!Igr@9c{s0wkZ`~m>;=vT#$`K>Gb;_d0N?H&|=VFPIx(~BEn(683?&;U`{Qcj5{TmUj zQBb1^gG3ZsEhQt>IuJ*qW%>P9B!ZUZ`mx=L?8BBVl<51mbXz`mTRrIpGHk7cQYg9S zh%IsCbcJ$!?@{PuMxE>e+g<)y^OU1bYBXuk^BTJ=yl!i|b095+A*rWVeAmS{)*Ce2 z&cJq5=y=@)gC^BUAPssUH@D)7D8f{I$LV@P#UbX8PR}p4$IjZq64U32KK8%$I0ip` zx#1l-k<)SIvgi3q3nl`y?Hw69!gc;A?JdA#-E&96jj`}r`^_j59wuhm+pCcDLr1zc zGpXgyiwv4-zw4+T4~Ehm;+|@^ok(HO)19&L{g}6uc5LOa%(Y=%xW*h`Kk?36!dhmwq^Jd$w z-|zT`tv946LpI2TY5K82tEFdGXWwC3dQ-C_&Ge|1ZwiyWY{Q`I(8I*S^N#rjR54SS z!4Xf>&kXw4#oI>EnL4k$c;7D-2s_`4$%U0dOuITRT^|w6FftvV9UYr9$wIAm158?? z;4|oHr;XqW1Dgg-gGdn!x?`j1Y^{$g0mJRI$f+lJu{PEZ%ck`H4a!h8K8* zjJQ9aH+zNTPScw4!k;SG+-^^5lQ!A-nw)qwlTcBh*-djpnNDOnp6!Il#oQD4hJ6`I z&q5&B5B)yApTwB6&8EeQkwVJQ1|h5ByMZ zU~YEd$>UA||IZ-*$E4(k-6jkQ_ojog<9rHGtzNNm+%lZw0X%MInN1l5nk(2F?B#4~ zGNR5;!9Rl@#K=yo;H(75R<|!ACBx%>sZ_^iqQ2jCOXZ|@uM7$ARwg7y_+n?}gXa$Lm3S&$8VHs0{h36!DBZ@ZwU3(G>H(K`WbrJRWB2{sVE&q#=c22t9Kpr2; zP2W1?#IJw`Gt1}e8~wm_EZ9L5qc(%sk;x}X2j=EE^2eSn_iy)C2*Ig=jUa+ zdDchkizTGxD~=_gSuL^zuo}A~RnW`8_!O*EqVRJ0<@fkOz%`B zCHCgX9h<#4-#&ziva(~t-*@0wRIH?S>3kjO@J9MJ5~7&QCbF2sfL(ZVB|SSbq1wsN zaAs-A^!N2;hU-IS8A@M_E$62R<;p=z}IhA+o(KE8bXdRCd?ha?j27OIG#|;t!%-!7ZIK_n@m!4X8P;w8g z9^PqYHayhdwVlYQM!z%29@Bf{h@Y>~?+w};&rV=|WH0DKCiEJ043gv7IQLr2aRYCn z*}gnbBdFpe$I=>gp{IBstC53qOSP^>Se1Lz(&tj6{>aO768~{@n@$wrIfV=OTo>O$ z)LQo=MYjpa@i_h*&7w`AH%m2|#%LY=32LBy72`mGZ~l5-!|>d7x`E#W?xvn+IG+DD zeS^N4;`kO_qe+ath@BGXTPe1e0R0`b@1nhqc?18APwD0NW^aE&-}?+-6ZC!jGG?NG z&>Vhe5-6IdAK=TNAJVIU8q)U~##(4Yj1%Z57=KiN@$*8AKcSxvF!H{6Y#Dv87hwBK zA-12>FH+8bnaZgcYZ2GQSbtT4#_HJa(?Uw);-%5}YkK7zIcBF)oyH~lO-kc$3u*i! zSeH{dUITX4#nT#;oq2Uy@6m&l)|En9+3FI#TrJQfqfnFX(Zh4(nbBAs+Z(hF8i#hn zoU=}Dpw$eh{hR2VHKMl)i2bC1*h$(<(GQKN1a*;gi%=W1McX-xA5uOfHj_nFj5T@$ zW+&+Ffq#0er7{bzwS)0pw0F?<(B4BMdLNCw*$4C?eS|dz{{Mh~fqx7C9_FvXXW6t* PJvsoSOFqUX=)?a2(0f#y diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/CreateViaNavigationPropertyTest$TestEntityLink.class deleted file mode 100644 index 4ae271e432f2bea619a7d6cc49279b53d9997639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5951 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 u|5>0b6My34WvhFdr2IyI?<*71bu+W!E&xAZyy diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FluentHelperEtagParsingTest$TestEntity.class deleted file mode 100644 index 0f9c2bea21c6b12b014a30653ece01dad3eb5971..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6232 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestInput.class deleted file mode 100644 index 92c51eef81a0d61554cf15b426affae18a3727b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7421 zcmds6TXWk)6h2DR`a+;>(km^XUVsEzx#d#YCZwdf7bj(!kPa`*DALAOB5Pe~15PDJxmFVT=e%_wwgf<>aX zZFlUN!&|k;Zr2>2$8IfhcWSOJ>=t*KU*qy;t;zg03u_B;;0TWg%dIvK#hMp1d^X1- z7J9bty|U%hI=J4HkL92pixLeRbSBxvw}VFQ>EF{=@3xsiXX`zx zS>KMLNrNhDUL&wUi1~|e&QE>5zCJk(sw>apKzJ>-9CW(@jGP(XZkD}_pdw8)_J zrSXaS0u&C!QUYfeO_1N%XKLy*sbyEw;aQlZL>GWx53KnxT%HF|FYFZrs-?p4W0PVk z_0h863NwSw!PGtLWe#&LH~6%}!q9WsWSKrSXsOt&-315R)u8)%BdRFNZ<_??WNsv9 z=UU&=GytLD;1yT%!*RO}Pm7-0VSoUU!F{qLgI~If&s&>(r?$uf7TSnTOx}Dmrhl+Q zyTt?xbHQDvnlUGDJlmAGTQU(@D$obuWtq#u^di#>TrWhj@OKd+TP$RO zgRt5Pc?<6^V$8Xw<;X;3Zh1b7j&m26VPh#k+je&B1~acC+%05q2!sr7A%k042Dg$7 zZmA4@-E=vNOd0zvTR2Tq0Ge)A?6E_TTJm_X62P>XD-68kaF7|uYIvdRd2aFp9IP8uDST7>@r z_v0MpQISH4oZ6I#aUF(7N2YSKHRCwQ-gVYtg?`#y;rlaTBbwzwB*NGcJS-ACT{!a zt&W=U33;l-fy=f~FySNtAoy*^K3MmtEpsKtW{S$R1DTu^;~Y`7q7LQ)nY4(qgVlS* zTn$B6m}qi$jmM!g$TE3Mw2Do=lC;t_MOGJV@dEAV6u3)5v~fid9xf8No*dPpYGvOF zA`gMAJW#d(F|+SN0FOw@33&v|y4&pD2MTUiR(89Za_qLe9o@Khn{Szqrd6d9NdY9& zmKQ}Rwo>K;E*{lNm%)RCK>;{j;=6ZrsP(K5A7yr{VB>v>jxrD)bM7+)T>qJtH>g%j z)j3c<0zR|qmgWVhKa~~juo1U#fke@X{LyoS>1t(zd${%;bgxV5;9MFxl*7T1=5fsy zyW zoV>%x>Z(B68J(jt&$HWW^od#xF9({1( zt03*9tuD5f74JL9uUe#e9+tYYU z&_k!`a{p-B*ecUk2EEp3FKH&BV^7EEfC7j4K|SxiO~|b4eR6yP?YuHQFzDLBileQD zhBnyu%$x%popW_VSU_vcpj*0?tqcu=#;4XF=6Z*zbkqGqXr|Wt(-e$auB+h2&ynQ- zZN~Miaff ziu=qGj|7>7e=6|5&IuZ#lXMEt65fWWOci{e!fZ8pe}_i0xp%X<_ma8ObOv-?dO0T7 zouyH5Rouq1S;_4jJ`0#L@LhdP=YJZ#_$yuh18*nj13cwwq7HqC=ZK=Bk02@0wLK%( z@rFM>=q4oV?*1{x`uTq{fd4O={IAeB4X1icWc%F0KDY51lWIt%43dhgXIFq>j3@=I z0RkAB0^Fgy8IWHj0#u=}+*1nC-Kz)Ymjm|lHTU|O?qz#@lkFun8@$*3f3VlX%y3el xx3R}5nuO#-G?kj~+f1Wrj6cHXJAA6Jnn5!(i(gNY_hFh#9CV4k$NLb?{0+Z`JGKA- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleDeserializationTest$TestingEntity.class deleted file mode 100644 index e3d5b0635bf2fb7d7bd68f7c6b0d45c3b5dc17e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7241 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$SingleEntityFunctionImport.class deleted file mode 100644 index 1e38fa44469d0855b320fcd12da82baca651198b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7887 zcmeHM>vPmp6hF5Vvb(%X5e3u;Rki|Ib$qfFEu}Bqw%8ZqIL@TW?KW*T2{$*LmT&%5 z&ghJO_m6V?-DI~-XlPQWtuW(<-6S_Bzw)euzYJn-t4wqbnj83*@%%I7ry5O`uQ$~U_x~7GXCv;X^KI(bCG?mD}ue2t)1S=4pS?y{v3+{{t zQgB@cj85}dvoY5U@$khINxTGKL!Xwq6Y7paxGfPvGV_vuY{F+(KZmlT*&nzCmnz^4jPZkBFLC7 zl8$?*gj+_O(@7QBo8OtpQxY76J~jf94oOphd@#!O2oJ>b$CCj$b6b>vvZC4P(@@co??$CNIp&-V?4nD%#-~`Vc81OZuWDtMO-%>4u}8 zJ!OV#>Lp+Dxunx6Nj&S95$*|I=QF;wp_-8#U|ssU8Fvu1D*8j$v5>7{gtifHxm~rB zo}xai+K0%q`KsfomH`IAxf!{D-laD5nF!1hD&6$dTQ~tu5!I?m%T^%M;Ss4$DaBvU zwz}yiSeeSmG;j4pj5~UA98UVCP#I5k8677K(dZ?OSVB^(PDvR&j61-%UOyH-j4B

m8ds|x zmNKP8Ek$%#5|xVicmz`5h1tk;Rn8WX`JM;LBC2v=r7TsVeR#b{YIKR-JVO`gEk>8? zsgY1(j;5N=oNuAcnT*(3#; z6&hwVSwe;~DZ}U--{Mx3l0nv6sV)w(9LKB>qfXPdcTCRcR!)Sb9TyK~5wjI)Fq$Z~ zMwy(4KiXXG2!ROK>cib(@7?6A^oaL*O4x-c2r!ZipP0f!*Bru~CpS&J8&!HGJFmth z_qaeP*zoTcjMn$pD4v8KQY}Z;L+f6iC4=TcXsh5G@f5sUAP_4cOuD|f{xiCip^F!x z-1EJw$2W1VE%=eJ_yp#-jBfVSO3FvNu*7>Fr6XgDt-MC%s63R+N&7I1>shek`8?<=vTHF}cy zvX$gCoH3JZgF}emC&b7)aGZ555oQ3H;3*csCdRefl5;e+vj1c(t$>zMcag z=D;)hkZz~o^>Xle(m&6EC%Qv-VS}E4%7uykMvj2L5p z=Q*f9=c0Z|U%^?uKq-%A=AzxB=8lXb{{q#{K|MuZL$5x%-_`va`WDwjqxcPAozVk& jh>#n{aS}X#!1^GLQ}l>hG)s?Zo)#h1GCjqeK6>yE)N$mn diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest$TestingEntity.class deleted file mode 100644 index 92b0f64389163e42934fa6f0158a247568e9ec79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6919 zcmeHM?Ni)D7=MDa$DMtJR!6BC+Nz*6QR{1|Rmw}DJ)k3$j(%H`2!P^dED^ z&ggglD97J!?jQ#TNgZU`89(Hb-TdQOtY2@dE`s;=r?)P=E|Q-<3I};FSOcH=@k(-B0d{yp?6n7 z?56p_TsBjoL5D6``Vos8{_^@J4>YE@{a{)~5yl|q&~SYpwxShCOgeOaTE>ZHv0h_Q z%7>`x(B;ko`EN&mPC>857vgP^i1mn1#j(`P5KbH#sz2aba|L>5whbMJ2K8KCc@bS1pU7=yPr%pmlWDuHc#=G2Du(!tFox@qY^}>J{{fG= zVw#&Z2d7~a6}slo#eJ2{Dkaqrz3ewk~46l(yEn zT4mtG>`n$OT4N9~#+}JQ-4rkyekz)Fr~+-RT4NmP(NNbMpC6t*JtI8JpQS8q&E-x<71{^qVT~U#~ig^@A%dZtdaOjX%rb{Ob*fK4!t-T zQz~I?B_Oj_7E^IJ&yuEbf#YoxaR&x&KN_Oj4vihZ7kCA}Xm&_D^v3wag8f|^qVJqb zw~znYI@M~ETiu*?TW+|aVQi*aI_02+~A7G0o-;&$ri@XQoIXY zFEF9e-4GE^YSq(QCFEGxFwkbf7He?#I>L#EGYSmhjPh_sd3$G+X9J;^ol)1_Q1Zkz z(WS+7&~!CK!*|-)Xr3Hmxn~YQ&kC$G@k|E79)Bjnc@3eP>3Sb2|Brknq$E|tg9US? zH;a_?5>RV2G1o;TEiTm}I#J+`w;wgj5PliF6#|?NPILH-?y>2YLvE%xq05jg1$+fO zPfFyzMH{kwz9vWn*D~9ARbfIFai68L-c%4E(XI1sUg(wGFQRBlH4s#UXkq=2WB1DPBqx4wIPN$d5L zwBs2Yx7_SLk>yQ^5uV91S|UkMJY|Vk2B}r&wQkSYy4Ys9kClW_wLpq;IKJk!aGyi0 zd=`sZ{c15-Y@A3faP4Er)N+=@nn~tLw6>_#A3{hi-ITw||S*mdg&Q`d$b=y^hpK{ZKxIep3}~ zylBN_DrkS!51n*CT&W*EtT27Po;#8-zPb>byg(${gfW4ZrwW?8=(;Y%=zm%t;7NsM z9eVPgG=jlVPqI;=dF*aa&J}vt({!~ARjA(6W6!h-o)@!J#B>);7kmi`$h=H6+NEeL z;g5hp8m2RJ7NZJA12ly3dAfk7i*|g8YPg&CZ`$!?``n!yv2#}s&7tK_Z_(SJ(nY=l zQY~- zpU|hEYIv-H*IT$7K0m`%aj>X|hc43tc+Qyi`!GG*hx{OYu}9u)tpw>WOOkdV$FDom z&2!#mc3;BfDUQ)Mh5Guo4}a+NLNo{UHAdebLCsiQCdjhXDs%_m4A5jp|97p%E1SN3?|T0L}aZDAkxz diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/FunctionImportSingleEntityTest.class deleted file mode 100644 index c5df8a5e0f107f1cb783f60f3fa55209f0027733..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7346 zcmeHM-Ez}L6h2A;$4>YQQ2t60khTd>xwQ0;P^fVnV(OSQHpwv48Ag#du@YHpw5ts1 z3-kf{0DXd9cc#p=m!0Wr@BqEeJ|v@U5a`LKc_I6|-pZc2Ss}qUG|yE(&|OXq(b(bDOzEu5ODh=60B0oCrKi zI^3(aJKUEI$6It+#gmR)ZGtRE2Q)esGPSbR9Y#4M@N@n)sbeA1}Q%vQ^lC)1_o zq((!v4O~O|U>eitaG85Tnx33B-GKE|9=M|5ZB`V_UvVr}?3l8Jhaus_qk6eneOP}| zFHKeYX;7n_(gIW(9Vj~9Mu!Bmdu_MlaYG?`$LI4dYl~ZYA>V8rPza%GAasrO&^5x)HKNeHtlOLkUCp^|N~@(yn1(&;V4`t41cjm2g%KJoGkIhK zCtMZE8OEX`TfuySmH$V+7FOc^qJap;(pyS%(hi_u(0ELyQHHocI_{nVpBdqrjAbtb$CO0#q6Pl|ZsYb3BeRD3&F5cSu>v zvRLDs4+Qs0w%JKuad9NFd5WQD?RIn^NWo<=P0t(<*J%4(79m$m-!XAe$hgw>O0{x$ zaM)Ty#MN&wsOCD~GrMlKlagZ`{BRuS(+P*UI0bDv)5L+aiqdICpYPfmsh?QZr- znbKZ4Wh!xx-?8y}9xdZckR=kPRmktvWU~1=Y&j(85m$AuG|Ck7Srn=VuB+B;ir#hZ zc|fM2vXNynXNL0O&4Lu@G`%xOC+UJlr$)B+s#JgGXbojfJ!sD}zp1>Unp)g4-PyIw zy7usZY&k-sx&3K4p>1>;2U%y)Q)L{pW4$Gf&WzN$1>Q`6HeH)ly#+p|ls~`zV8sy* zv@fZCP(!{@pR$o5vkFYo?_=(4&LEN48LFOiTXC+c&E|T zo`?ooP}uFtEtnL&C?DYAo?uKIdqj%~#d$%&%BZDzTNNua&<0gD14fmb+sQ0*Zv{MP zNvdcKTdkkM#Hl8G$sSP`Sowela@{A;>{@&R5o(6X!1sj8U?tI2^&YoZH7YcC;9G10 z%|r<3`Z)URz1aNp%VWNq?X`G|7MrV7THgZ(a=tM%2$kF{xz5Hk!fT5?N(@YaxWq5j^4&hTw zG4Ynd1%834L+=3*#W2!*vLw97cVFUN{wE;{l8h$Hqr6K1Rnw>=2#6?*Oio zbP5b_OK6v zk1MBP@BwbR)P1;*s7Dg0#}cWpLb<+m>T5B!4Mndfu>Fw8X6%yf$3(W`F4=xcWcz5B zY(FQm-Pk4D^F+3fcgglgBHJe!*ls5H^hF}uXS-zkE0OKs|boC{L4k-j8K;kH%?&s%xVMz}@{9YWn{9 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/HttpResponseEvaluationTest.class deleted file mode 100644 index fb24772344207310bbcab9c531a3d1776ab1b7fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10450 zcmeHN+j84P82%MPoVaPzrY)2fs2WZV6q!Q{r6~o+Nu~xjDY26ot~wiO9dDw8(XLYb z47>q1-0=cjFatAi&kOK24F5`Yv`OSjwi1|5bCJlB{{8kp?SEM9U;q5}2LLR?N&&_+ z_=fvtSx_#@J7kZPP0uNF(kYv^7g%L(?UpSfh~rt*E_?E4xlQd3^~(>0=&V!T@mx-q z_lO-3!92H#Km{1r;CzT?6L-73wzWe|p~0D@ zYBTC0>2zJfM|b#cTE6Q~roq+vBH_%e+JxgpWu%X@>p1nsc9d*}X zb|{eE@x3jZI{dC)oNBhIF1j76O8`Ua#E5jFo5XcJp>I*W4`S#wp);;C*J3`Z&F z7WJuX;tVa{bMXGV0N+`OPL)a%(gC2HT3%q3aY}POB&K8jZk5k!6O|or{nn|(zVOH1RWos z^ZadtpdiS|QETL(*nJ6s}Fll0jaJldU zQ+U1}rGV1BjP`+R(H3)MgvU4dn*=s$D5f^_hXEJz4N~dEl|eHCR)~sIN=i*&N5>dL z!b~J7!#QN~eEOi`RGf-aFb8GjlwnHxjZ$=W`V`6(^Qii$dal6Iqbbw$(LA71Vllzo z?G&QSI+L}5Lq*%ObQ#-AVNr809)3T;KV;Gj>j_ELFN+S=U_)3(O@tuJ*d&Fl1E3o2}!d)i!Iwa+h;iHOp1ZW`d(zXZqPN5ue z-5nBi5Xz|X4#GXwjFVfVNFp9lVH3kZ&yr{a*m<;Xs8%s>!Gb2#Ga)mU2{NFFbVhn~e+%nZBGer^~zEQ&NMc7UnxLD<)?5Lujb6GjYE zUBpWz0Z28O;<$o2l$V$*Mh!R$R!;9#kmPcTbMl7I*0K9W!{y4$F-&&#

Sd1*vEv zw|$0e3I&;f8F~!7Rw#&6zs(ldFNVMg68Mmo=EJdnU0rd*yTQ;kalsu0f-I?3=~7 z;d)*+K58qY!Ca}XG-M0OykA=naGTemHFuA3hRj#wW@?!Dkk@l{)l>_UFsDu2%&=Lj zj!Idj3X3U}r#&O~x2HX$y2-O2u2j!I+{7yxBUA6H(5HB0{(2xZ=Ya1pqn2ZT#;H5)6?HPrKRZV_|Pep z!J7WvV)tu`hjFIm{Yv1P@}Q9Ny6RychrBhsz&Gg%o@UYD`rxWKJQPxZI~rUW6b1@# z7nxDovjW`12g--e0^k9kqMRf;WK@7Jkw9_j0xW89L3t1@@&YU!INKC8b|~A!Xk(#? zpKdtZ06aZ}XRKa?tN1s8zs8^dli_s=isAJPOo!LAa1O6U95Dmu@&5(9yM?1Q`Sf>~ z{dV@!ukg~JA?+*pecnx01nXcn6Nu}epk0!Z^&~7B48Hs3b zAU`qMn+jUY+gk}}cN5Xx*+(lPs^qPdh&Z2sSV=^DFC*g31jO}3L?a_&IRUYii1=X+ z#3zY}ALT&&ArbN89Ed+9BHqe@_)7xf7~Do*8G}#o)FL2u0X~J#U;#ddFQ5X|e*xW| Bj5`1T diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest$TestObject.class deleted file mode 100644 index e0990c6b289ce5792e757e20c7b8288866df0ace..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8305 zcmeHM>rxy=6h6Hn?8OiwkQj5F#FT=USuZh~fG83I=mIeiVp9GYcADLR+3C$p4~F;g z9jwwS{qHmQES6u->}3{acUo(){6SI8ZqJ-Oefm3JpVNK%kH3ETjfj@$d7g5NUIc!% zEO{Wyd)9$fu8KxEu$twnD?+;**!yMMl2$|5+${_BwOr?Jll$d0VLQ7{)sl|zp75Y4 zynt^(%^me`V`q<7Wu8VDU5M1WmRBpsPZ<>>MO%kW&S;|2eZMJv$E(dU8e4=mX?b$n zazkFABBN4QEu*VXLQgskzUmz~fwSZC1;#3fBdc z0GZL$^i0Cg4qC;^y70Wvb)jG?A+T}4ec!Q_-jPK(Fi&qX8t=lrTZGyi%{`ACYt!z?ME|AHXGaNKD0E<~Q7_g$2Q0&+Lm!^lH?N6kes+ZFIv}WQomToHDX$RrjqL zH*O;`nn;Z>8mZAlYBW2k(Tp;h8K=hEhAntts4&^EWVLQcz{g)T0Y*|CGK{J6t{H)< ztvtrbX*7i|V}E##NSRNMTuXbFnDA>R9KwCMrz6@B)qMxB9nT3pgDb74qM8UqWC*3> zW}zDK*o?AqoUApxV1div;;CeE|8k70dm%P1Ssbe$^JKoH`jhZ6w~V=Z9gv_CzgGJNRSd>4CoWOYD} z?^fty-E^f*V=WA%dIKrL@>CLx6(@vBdKA>$ zdWH|Q6sb*Vi%qh%X25>2JD4qIw_0H|)EW@2v$%nkg1uXO=*O`+T453K;z6{|hg#&> zwi|H;VXhyg6GqNwK&9c90TJaNI#^?{;fTbMVF>VOF-c2EV`9V7qm$B-p8F;c-hyp4 zQCZ7i3$`*D5*>18$Z5@0XRRm7Y{M8*E$<9H8X_5aI{`k5n(gSr=t@+adR6Z3{mbnF zV1YhoxicCk`eE@*2YW3ip2k_!Pj!ledT|!@R}@*WWj#`VjrVV}GcOq3Ia)-2Z22hN zVR`pd)32tm4Yg)fp+(Vip0PiNLpKdZ4oBoP>SrBfdIJxt1yQO%463&dKT-K`6_+xb zBJ`{L5$bo0uJ=u(%5RLEN+;@{cpZAQqRw`x)KB7WP-8s!sjU7zJ|{UI!9x8dOmtL-t#U;7a&KF8=*;>f|UAY z37;47c?swPuex_@`qn9HC|J5i&%dmwoP9#OQ=`HjS(K;HH)Z2J= zT-!S-+U8TW&Ct8BBc8>3omuE>T}ajXzJ`q-q}X^bRa=>EwlF+8;~ZumpK9ZMdH`*04l1om z<1AX9fkJcm(xySA^mDZF EH*PK=`2YX_ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ModificationResponseTest.class deleted file mode 100644 index 1008ebf9081b96d630d2de7b4ec4d04dc230c2ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5856 zcmeHLUsL2n5bxm1vI`ssdj31li+C!im-yh5M^Qz=>aDea!b+9CSVJ=G4kRBo5^&3^Xs1Oo=*S$&+mT%Knp&v!;Ay_N(R2B zO8ci|K>R?&z9NYqL?R7+6`uJa(IgfjjeKEl{XUHnD*as%vJ(~%%>+N7DiK`KF2dBI z=D^(yDcB}i;ftAi!-StzC87ZhUDP{aQ3mTKQXEG#R$sc!Mz>F0Jxr);06e3Smuut(gmammtK|MV02XjJ;bi z8eZCp>Z}%t_*k5ITT!$ndn!9NWg2K9t2S@+;trnVmZGg^DT&k;51$g%HwUWadtXzn zsV$#9@nU)$a!SsI-M$n*D@$Hok=Glg;_WzzSb#C0QY>bEbY0wsnTW!rCh?zA(#Ufgqn?sOo~Txp~NZST$D7$N=$WMgs#cC zYul#A*eJZeS;F2N(OHEvsiJE(3J4VpOltn`B35Rx63X4Zg3(Jxnh<-%jkGYMm1Z_! z^?$5uWl<|3s|W8q4-9QDyO)M}D(0E$2B3;}+LU4>u-dh62gW3R78-x+XE-aDy!tn~q4HV5eXn zE0?qqvQTVVI5w3*ap%5Kf^UT+DK86(l2{Q|_|t`RR>)bHV`8CFWHc?CSuJLhds=x0 z*duXSL3U&6dxG;cD*N;*LYbsgVQ>qPilVKsU=x<$^#xdfdk)-NyV^1`O`fF=mRNgf zd`xARVBf~{mPA0JBO;j@j~>=^pD72v>rA=dg_+zll3fdidtLdBP^cL)cXha!~${R&%M4m_BcEKQ_ho3PC}m018#RfjbP zew^}#Ju;R*?@BhugF1YQ?ZrZ=TZhM&I+=M2K#IFeDQsAFu`P&OIlqT^yVUrf0vE9N zh~3p0cn4mESsc}~(HxGOFpu{&ycS>)XYSzL7bxxEfAbGm`e9}HH+bzY9L>NR_-oz< zyxzp$g(H@kb?U$lGd_go-isoLRHId`{$gP~;Y6-tz mN)T)C2r*{hL-+{)o2c){c&$SNJRHyB%qQ>}_;_D4bN>R{&6)iG diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest$TestBatch.class deleted file mode 100644 index 30143d81bf12c987084756b1f0b8fce2d23ef19e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6853 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataBatchRequestTest.class deleted file mode 100644 index 6a863456391c7b7d0a9a1168fa6c7d73e2af2b8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7895 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 E7kg4YDgXcg diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataFilterTest.class deleted file mode 100644 index 08a4eadc3616d6129d1df629d779ac1b4440da9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6260 zcmeHLTT|Oc6h2D`!Uh5*q)n4v7A9>;FH-N9KxsT+63-Zu28WsEg%Q$X7NlKgwPK)u zEHmv)``#bb=~>AbIha_Piqc8)AZw3x^nGW~?w(zJfBpU29{^B=yA}))SdEnDXclQ_ z&pmJ*Plit9wjD2!vF}9wzT>;v4W-WlN1CRy%Yrsj&iV=(t31$5HSui=h6zk15P>UN z&ieKq^E83+jpoBzjitW6T&Z6~ zEOqI*K~F4GX)M{4gy)`;B#dr4O0`DwcsTaYox3J9YcZ7!#K$FNCpSYr?`OHJa9Fag0_|14pf2<-+F*mr-zpk9L@1!b64~B}25I#Q4mowimcjM0a?= zqW_Y`XGm-UXuICN+hTMMdv0Utz<{uH*jPGjQ!kRzVJD@-PD{re^(Bj_>2v66Z?C2i!P6Y@uq~CQQnhT%4pVxsPnvsN6BdmN@nvy5w3B(2&cxGm ztf%uPPkS-W_(7e|?bsQwL3qWw1v1>0`*tM=DykJNOA%=mds?bNVRT-+$24wdeVsGg zR-floOX?=eyIe}QmH|%r;qrU-Y$q4M-?N2W6u;R!E`YnUcNE5)YYLy^tSfjD|G(k# zFR@(x$mLWp=WDp`;VHW+&Zi^}LM@lNrLa#y=sZ26P?bvty@uz=Q>VgmDgP*U12}^w zo1{U$|E@a;dX6Ywuy1D|zDSO8*6hPJ9?K&<&aVbB6Z(Gg`P+ARasr-w0QW)34ov=( z@3+vD&C+&~#@{sru7pxJ7+oH77PX)$mw+0hc@tLzY5+)CYRD z5A-nH#&?F`WB3H`)7bhHF>NS=gZD4)UWX<8{|eH6iNSvh-@_ePgrD%gk;K>e2mbai ANB{r; diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityCreateFluentHelper.class deleted file mode 100644 index e20b161fe5322499de2292cecaf31b402bfad8ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6709 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest$TestEntityUpdateFluentHelper.class deleted file mode 100644 index 6af35c90bedea6608c1afd88fed71922212c37af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6709 zcmeHMTW{P%6h01VHoFR>goIlQn2^E-koScrQdMemQ5NM=HJ6IVi9NeJiFd}#jHBjn z@qz>rh*y3T;u|{`?`D&|q$MCl(b}H)%(;AXE;Gk}{r&6jM6^oZmT1hQ(CDycxv{lF zcFby_YS#=)YGJI>sAi(0TEr}CtBA)nFK zVY#^P(SsK$^lbCGJQhYA#Qdq0$}%gIG#)+NxG-!dO%lzGdC9aCvT2~vy<-L>JX+{x z@|IU{JQmp@r8_p#`?r3c-7#V%McP8 zTD39$DaE_U55kxk<2OXi&A%xn;t=c}ndVc?=6$N(Fdd5(c-CrVql7(r9mR(j9_ELQXAz8tq59;I|MI82cP`?=o0gtza_2=AWOyhJM6RN;{;0zC+Pt9+Jaze>5@ zPGS)P6_cWP0e@+%PD87-pXbBsqDy_LjCeyxm+tTe&n%5yJfBks{$>hcIhD_(%OE-9 z0#T*BV>Qx$ADvuoMgeH4$ZvAHk_+46`dEbgIkPROlqFr4sIna1QS9>EY@Csfpe>AX zA~V8V{77o#m2_FfF%lyR+|8BP)3LJ#Kh!$e2a|z-8;IymY)5e~2E$O$3`IQ9%A9)o zy0ys)B3H<(5?fK{XB-j9R~ZWTN8^`tp}b|DG!BnA$6#u z9}L1YN4`fAJI@|n8}>Vi`OdjeBsx&-RS;f3me^0Tcvp?Gd^gaxFjYv)n_Ta^LrS%t z53a2{K}QvOmWmiB!0U?9DO2oH`j_c}W-N0l9w>X5IYoeGIvhG(rv*ta8^I4Y$YB!6yPj;_a}THGb> zdYnfU(h2Jm06SNgu4f{QMY&%`wP!kP;ak8|T}rN(U4NOI5`BrU10#MM=rRngdZ)!z zm83+AaOBIlLZi*Rxw4B!Ivjd(3tZNfvE-9SZ{b@C=FvTxz|$E1N;HY<6h0{1n3-_4{)8zo-6breT30CeM}EPRX(M9i2E5g zHz()zNe^y6_uw``pJC1z6#qQC&(jyU6D{CZfn1Ls(N}o-8sl+{s`L$>#^}*Me^L%9 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataHeaderTest.class deleted file mode 100644 index 47dbc9bc6305cdeee4a41539fa4362c974da4a2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10272 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntity.class deleted file mode 100644 index f7384f72eabe350ff909c61fa84ae40015db6836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10874 zcmds7>2ur06@M$s;t|3K?boj z4n=a4-c6hTCf_>K&a^ZAnE#^xNTV3y3akoeqbCL^|t8NJ)>7|+afUQp82?L8otpLCb#QCKGyfS-Q#Y3YXvVm z+~#e6^{8h!W)nDW$e&fmxBTN0jWe23IBmn})VEp(0A*Bd==!6zja7YRhf!NEX!VdV zqh<32MwRB)qwU7M)y*a&QT&Qq4sQzl=$7+XuD)Y+9K#P>&gk>=U45~r-@*RTU#rkCJ1V%Ngxm3sjYb@_&KXn7WP zdBbspZ%B+Dqq2U7`#yKU^|Z;ntY`X%k-9N^M zNnJVkzV%d;@0XLH34JrL?Z~&5fiseI)fdbAmTiI&MiaVjc|H&s>Y-TqUDy+X(S?@b zGAgwK(7^H3otrO8n29)dJ6sN&UROqQR5u2;Sl<+m16kqYs|kzR33@%3d)@=Xb+A9A z!A+5th^}`_t5)PZt*t21T3j2z`$97`Y5&!V)KdmXNZVNe<)9}ANa$D2iJr4tESDH_ zurEQ5GPsY-@^q*wy+uEmq?>dL8+OrhEPsj7>|D}RLk~W`3mZiE<(omb#oead{L*Me z+pu>H_!PWHgEJi-Wq?U9e-(RCl3iE1U=$b1^dm-Vr(3Z!;?SF)&Wd#B`xc!4EY@Ec zar*EJ7;h?vKQly2-{m`_qs2O{2@td1TA zn&oMhV_Gg!1p631YmXzOwBhCUT+zk*lK|gLt=5*&THCX1?o})2w~EQJuq2>ov>zKC zuDuTLTSL(Vf>1QoP&CyBMN>@$&RSSBy{?&pdm2&|t!w!0ea(kx_)!mxCefiLs>zD1 zrUaH7c_;&`+Y_#@3Aa;&bP&xCQp7w2s#Xbn%RW=UqgosX^B{g6c*)#B0C$yzSjukC z@Li(GqtZ*i;Hh*t}=ib z8cIjYgkiTl^!oyzE8CS3ttD}*fEFbY+F~f&Cd}67j_GMoQaH5u&eD=5_EaR4^@x_W ze%ua9_SyOiELAJZVYOp9NE^bu!*Dx67u7X#1?;Yvl@m|_$NzzJWV7tQl!`(DY2-4v zk?oY$A5Pd&EPT~th)7+U3m2OY(>_mb4QWy^R1c+csLv4OOHNlVp{n8}W&b8U#&Az3 z131~`$pDS}KN*1iZcqkTF86nOY7?@37!{l_fRmDeq@{BnpXPna6& zHJ+U$bDCRj^Ubvpi`r!W6K2BnD0aXZx(UIRm(GA3MD+}~5Tgt)8Gv~aCR2gaQ8M~u zuBfi?LfG>jZFG%}$cEa#3MQn;_SGmyqa%_>oNR>qi_z5t#MU8qUCZPPWqQD9{q!c1 zYysx0JJ8WWa|m_iGhNIu)m)f=A16nOis2@G|1_gaKV$S>HZjjx-9QTHz^h4k+>h#A z)kNFCY08cW+%{iB7Yj!S>DjOB&@kH52c=Dss>O-59v^B<=rWJPVZ@9+Qe0y6sy7 z9Fe%~{bL+cZ8$hDQE3wQN`zCOGu_-Jdcw#m=syahhe$Y#jH{DK2PML(pivMBaSVVe z4(h7O7uHyu7Q^*(I1A64%Id((I-Ry%jvu4$-7~RCC^CY8Tuv~(CfnU={16@!XJhv z=^9<62|Qh+5|uGp!SfhZ@%{q7_TMkz{W4t{yiei%#i92pnx+}dysGBU(pPYw0Q}3i zPXjvscKw-euhLgBFI@L)L*K^e>!bmP#8m~%4cxzhYmDY-elX{a!5sO0jJ}Dr;yDsN z9RF78_+_m8ZK`2jf0a6Bdscw;oP^_b>GQe_(Ws7Vs;%BYH%OYJ~9i zLt2XP$ZvfdZv%&f3~{`Zg5yJlLrPon*#OTH+wCui?P)5uWm*9pp_Eq#63b8H$EjG? z1~f|jNLi<2ypzK92dNl0a$!N*tEV#ernd1Gxb|>$a2?>XaS?TKy{TN6LoZ=&54v`7 u_sF4OqDqIjAHfM7?BCJ`!rO~0kzLkEAPKVxK!oc{n3b~qjY diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectable.class deleted file mode 100644 index d0b28bad0993bb45cfbfd04cff721c93b7fd069d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 583 zcmcIi%T59@6g@?q;iaO{jceET12AqB0}09kexP)2pjg^TTa2^vzg+kMew6VJ?g&e5 z()(!7J-t2c*Z0RKfC0{PGz3%(S|*A~uGFiN+OK6)rPS7M3K@$RS*WDez91`o9m*xy zl7gH(a55!JIt|`R<%$_|T-Rpcl1bI?mYQ42aVXH+vRmaAax!0WkwAOuH$l^*u_SP{ zm+z*o5@=1$!l|?g416E&GWEy1C*F?#=~Df>zQEra1p!v)%GxRZKfWQHNfO=#&V%uP!Z+#Q3 adKvBQ6pZZPIBTQFdB(UCt~}$ofzA)ZuCpQl diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableField.class deleted file mode 100644 index c2f1a8495cedd0786d3bf480ba487aea0111e186..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6340 zcmds6-Etc>6h51_aqI?K(jRDnB1(beW__P{%%mYDVO+OEOlG*0cV%0NR&ph6CgH`H zff=~xVR!_FBW+r5h?7;AI0Y_ty{rB9{OO#tpXA^F{P|Y^*o5U8%tbITI%!&FY;%tu z&}O2trlI3zB2=C>O}gJqsim1pnP@6^*&H!3X1e*}YrNTEf+cqIa7<;|Lm5xpX;WHm zkBUdMFW58AL|TLE5#07F1eL?)i~b(IS%diq=*%sMV7|pAx9te-Eq5kH>{!j^aBXEb zf+yeQXm-Z7Yc8VG8bYkg6kU?l^VPg!L5nNO|6w) zfLqZW`n}Z5C~?42f>9lF;f0W~RwzC*tE5ryjTo^QcKEEu`^&ylYKg5+1Za@tt_+eJ* zo&yh+8VVL7HXMnW5h5@ECREl}`|)~`Fk?FGfK~eR`VAr0_0Vi!d0U+)R_Q8>j$eG5 zbCI%8wCk3Ef;V5~RG4*{Zc#IGJtpwIZ!aX2-2O>=?CJweChd2XzGf+#|z>l%gN5~xvkr98zB zVilpj2RG^+j&e@dV;NG+{bhgx>{=w_##5P9@`mP3CC%}KG`oi9C=ewFRPYpA zW8WZ|&MouZ&vBx@lkF+dF+U}(BDg(le3r@75S*l9sP$F5P1L}1DNi%2^iN3p3(0y4 zPaQ7zUn^)g>Kg?uiVmYb%(t_M zW!dG6ihRq_{BMLQ_gB34U# zrv<}YYj;X1)tZ${YZD)4rD8&vFiiKhrCYt=egHDtEzUI6$>=D8#ce5>_Fr;lYVb(} zmugM~cc&5P2p&uu_H$B$&my>4nu8jAeqy7&Xo+b0^Md8|Faxmy7P`2Lx}}T-0ufxp z;|BgI5aEI41anY#R}l_0@Zu&O&k!_+zl~pD@rR{%e}db;;oUV@!t=a;F@igIz38tS za2M|3-Tf0I?|bYAc%Fj?@X-H$2p{1uz{hwj;rI=B1Z2K}QXavlsQn8ZUqC&N@puAX J;@uoP`U_0bmMH)L diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest$TestEntitySelectableLink.class deleted file mode 100644 index 6059f017edb4f8b6bc748032da534cc3761c1de6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7551 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSelectExpandTest.class deleted file mode 100644 index 2eb3f1ac826479b1b78e68a8b72f32fc34ca69c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6827 zcmds6TXWnr6h2BzHtVJrdVyY`s3kyBz$Oe&Z6}>Hq!4DCG9;Agqt@1Ll*rQ7k~0Z^ zj2W1LcYYMZk)70L;>1cO+X4@3S@!4e9G#nwwSWHg>+b-t3}1F&fq_R9PcGGj3qOM4us_9bxKJ%3U&&NSiHrCoZiKihv*4Z2t5&^C&E{r2n$by zr6=yvL~}#knp0-u4QkOed}o9Ut<#~KL+9qOFqk)aX#RHE zQgSQgsRDPx!ntKQ9CY*n4UU6%i`&dGOxrYewi_^GdA5_bF3*97S`Q`nq4h^%WcUzz zz@$$5dMjLvV{S~3Z}ZkQ?LQ&qVm35OS{a+=vDI0dMtcuF&V@|4FWS`^PvYg5Ig@5l zCEqf$;cATMbKh`_-t+{o>XWT{?Knc#^_tCu8aC>*l8&T^F;Gp8l^9E8<$$sV&d#}V zm#c&igmNVgU4!Q=ml)g?I;2l?W8ED@QKnqgD2Z#JpzBh(K@Vy*Wkn4Rx;=qB2lKHG zDSdw$AP;>!Acxxb*h;@vVP0nR(8xh{TD)<1p4M%2NPgBKJ}FbjFZxuL`U6L#OC9D@ zAL{t;j8=;Y8#Nb)Iugr#i!q-k>9VrI?zZ4uV^d2%+L+Nn_t276*UBr#y_h@_+)LP! zmab#OZq78=A?QBVShZ*>Owr2Rr?1+)#&$DwJ?EZPvEaEDh|z5(MS`WVZ_wJgzyb|5ZsIQr6srV#2y1iKAnZU&V znJ255+zqv*UKd?m|31^$JhhhNBJYb(XyIJVrF7}v0`oH6(Y<)Nl5K_yMtK--hc%Y< zB_Ega4O{cSVJ4rq;+W#nGu*tx*DLSGm1J*ZF=gLS)%%vjE#W?u;8hdh#pvEA(jBemc!9))}|TG8j?CYSMwb1lX7)RarvdY9+I}+<}h>48|_KNIwEMnp7z6bzqhN zAG;ndX$f3!Qp4Tqz^4S#v1?J!cLJ?n5CqPxV(dKSE~POtBLXkRq~pwd2j&TganVhRgvaR;1#iP; zoB>?HyAyB~&m8I+jtAM^X<(y`Y}Zf7Hs8p0712Qw}M*si- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity$TestEntityBuilder.class deleted file mode 100644 index 678316cc6e5feac0831e7ee3c08b7c799d8742ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11775 zcmeHN>vP;R5MPHTPA*B)rsdfJZfW7lM<&h?#O>xsg(oo9NXDfG3eAd;LL&6L* z{8!At418ty#Ap5(hM#0#;>)Ey!JG}uZe_4C9PJg{q4^mew?9izTs>+b(gp6vD2x$0Z+Vo>}}RPM>uWnv7pZ7&w7gm9TwGZETFK$BHs!8Zyn+D za1~6>$$yKX@WoE<=jFr?JQkUB#GqqJOyGpg`i;g0bA>@uekfRzMQcuwFoP!SrL=s} zpi>_wVAf_!;g%o!jeyOCAs0$qY|v?YkJ(Dn=|n7!KXIba51TO>k8Y(5XYGKu8+@~V zg@r6~1oKci(M)_VlQ?GIy0x?b;$$o$G|tGgdG@LTkQ01;J!S%k@bRSIu# z9xx}&02B7*w2Z1se0fK71&`UXAgNe!FTI|@O?#!qBdIk>e*M*q3|;cD3F_D-KlH`B z20c){ux8Mad29xgo-=4>Z}lxk5s${{1>@Myw?ZsK9&9ns!e;9Dfm*FrC*sgRJAT`$ zOsuw;C3ZT@k_>8cFVxb#V7ZQ5-&TWJT_!baNmzbt`Jv}W*e<~is@55%+l7Snh__L` zo1o6KYHr}fv9<08EG|+aNKKiMT53kl zS{`SyCCA)$gxj(NRKvDA7-*gy_D@aPsG3rkHuF>me!GL|wRqI5K|3rG8waSlBxswv zo4(*W9ngd-jX-y;K|B>u+R{T{wm*C3T&X7!exRkEp#!&TYUii6qTdSibXBcnY&C3^ z|2%Rdf$bdBO}U}homjAFyFF0<#e$ohH-mwCm3{bkkua`0TK%8HQopS>=eqFhY_`P) zu8wYtAef7q@jMUV(Iu|n(I9PQ4Kr6&6Ad54(S$DEO`IT}3%#pO+>$PYX1gm)U|(p0 zm-#;%^~&E7vdUMKGkjp5?pbBw)N_>AQ<;#q_JG!TNID$@Ks`S90PL%xj zKd0k8a&*>cO0OXNyNCzl$&1nX&zlEhp^;i&h;3H4vv!=f;|YXducN@rY>o2+{?G1)lmNjEL5JQ{4?8UMN@c zQCrT}&4?pBDt6G7$CUcE>w7o5`Z5p0BnVJ>ZhvGG(L$%qFx5RF&WgPZywvK&ak^-X zagFfOu=e_)d)0MVwXB|o+7I0;8Hc~W1=6`4XsG(oy&8(958W%R>#D|r)UqL3pfcSn zgG4ob9V_)T9VfiEoVov5hnEzCzCXxEDfis}uDb8>uT)JLbX@rz3k**i>M>S6aE&H0 z-Z2gOwOW`iE|xq@tV8v#y&*^-5CRlBdhB@5ocMD9K<)O`{!dF0r&>dn1mu}L+1)Gt}CG*U8apk%S54VhtM(C5|S1mjdePD>hX zH0UHGNvEf-GI>%e5GQlQ8(S=jd{5hSRd^;&Gykw&`EH8Q1O0fpR9-Jkv{yR^jIJ56rJv)Jx)*b(Vir$k9LO6 z_R*fAbA7b)^z=U3arF5as-pLK4P5~BRcO$_xAGG``|0fSKTz!#HNKbdUuu%@_cH!V zO#(%)poOH3(raibxmCdR4fQ2@6JOIn^;`JSWj`V-75ldjVE@N~>?i5c-)H}hHv4IM zuSoVY^nS+vD86$TzovrpM{u0xiyXlTS^)cWsW0|TbN|fhcl=@cpvaCtN>_?xe~PYV z>`Q)Cy({ybeTlKnE>$jorwaCUdpo+;MfQ8($MjH6tW zALp#iKj%71E8sIit33zCEzo8`U8CD{hbCa>*XT2Rw?IEl)ATugNncZgJX)s~ZBRfV Nbtoc1-zaXk{{oy6`$PZ$ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataSerializationTest$TestEntity.class deleted file mode 100644 index c5fca2405d89292cc0046bb99ff7b28fd8332f5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12085 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatch.class deleted file mode 100644 index 393104af8600309fa117ca79d7be5427038a58d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6142 zcmeHLTW=dh6h0HuI5sIQr7h4>7)pVJ%Py31YpY5$ZJJ0wE6}<6+g!pE+aW={ZZzabFi3fXcXU;iuF5fw4e*NpmpNZ%$U2o77qb+OV z&T@(4Y>)?CE|K%SVG|lLAE+&msH+<0960u`+!3wh@WASz5dP~~mv=7DO z4C@%3Y40ysb4IG}4MsCJrIPLzqvz%qHW*DWLcaz*&*;=X@m*uInWdBLWb%zN!s>KK zBpmx2$TU}u4~*`lqHnMBR&%{4xZ52F?imDLy`WH!$B|On@s8koOai{*xU^iVL>lZM z-NmO(VT6hyVbkb7o*!rUmhd1>BWwAlOog4Klte(VS6~pu+fi5W%h-7U&q5)1Hh^b? zp=X2Kvq9n6%RJG-a=+)kcPDo+jd(J^M$7CFHh~YDAU9ZT=7kMp9xdJ(L07t7)~T@a z|CGmQrQB&w_Qfc}=Y*M)=~xM$1bI>wX?Qrn4!B;MZm^Xh-A8F~T+xK2sUv!PWU20V z^mcGhI44Y{=tsboRUL18XssaNPV*>-vsM$poXQy1vl(C;DW|M6S?sjo1;EWOcr43Q zA~vP+_=PXHkFpf3_o)NEnn74W#Ute*p9Ud!c7!o9se*_ynXjCE;d(mZUE$Vy(jubt zW>HORcL~9;OW=JUXL?UKq7gWVG7VcHsPib!3w0s0)jY1vbVOIG#U45Vu_oMP_ms!S zQ1-J1%5hm_Ebc9KWd*v6XY+FntgE;2J@9;(DPI9s)TSOs=9Th5=|znuSox?qgsr}v z$^*D|Zq!88k^*{rT$1k;m0;Pq!5tJEsgA1IWk{&KsMTDpEUsl{wV?UPUcJnzE!DP2 zmLuC6Ew!!VD8+~)){knE%2~NL1&xTK4J|Q&9GWM}Iv(H}`7*s*v7%-~)R0%px^;+J z)S^>!)S%Oh&dnbl+Wee$3LX&mvwlaI^$4SvpYFV!HzFhb^V65p4n>4f+Me8KikY(U z6y6xA`Gv`fzBq)U9cd*D@s%Gr^Gt&sn(e2!1|`py47*pKZ_Ls~M!(k>a_iya9^Z<% zD@SSxZpKm=p0OSTv3{`eQ|f6x#UZB-aug7??{;Q;3>yyDyR zFhBDtpahZpTKQ!*7~pWlK8lRO0guJ!6FU*wV-FBT{~q2GO5jjQBrx|ojDD+O?*Ha+ zz^JvRGZTv?oEb3+YPSjbNetj(O?FYGGlRu{wyP(No0G)13Ch~f$y^ja)QLAR}qe)!W*3p}U(^;G|LlT{!8Qe|d zzCpA2Z{q11zA{`}KT+%3Gjl)C3%}rLieAM3O#a2MB$_Lz=p{Ofr)fGz=Rx%{?pwIN zg6pgJ9p-qUg5!JzM<5Yhs>Sj33XWkVM~>YWI{KYlsnqZ73T|&zaBI-JSU*Mcw2)ul s%lGd2zK`b*@FV&VzcZi#TF^(Z#mBgQg6kJ}KZEzz=rj5f_fr)74Y9rrZ~y=R diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest$TestBatchChangeset.class deleted file mode 100644 index cf47d62fdd9ccbb4bc7b9a0c85130682e1c41c86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5301 zcmeHL-*4kY5S}f$rZGKwlq@+vAzp*>Aqt@!$Xc{wDxDhp+0eM8FswN0u8K zow0Kk#VU&o8%1%d@+30JFiMzZnM!yXDL0G;JRNZz9q-}I>DT+YjIB_zm5r3P&zOw| zEopgQGxxNEdY`zHf3Z7Yvd;~-b*K@z=k2BVC_3(*@z@erek!D}I|S5bdvR`UZ=DjT z?cx;c&>*mO<=1nqm0p3j$=&2fsku?S$Poy;Q{9sBkT)1$h%Xb{J-QaM#(Rl6&arucrTb+kpIEB0t{R5lb8EStYWgSSmO~W}9Xn4EpjJ`l=`3g< zx2Gb;=$RauA5%^mtMk|@O$&nC+;Uu&%Y^rYbo_;HaLn=)RbNSm^e{(bg%oAdQ9_}( zBos7GtQN9A!>9QPIe5-6Rz!1#(EL2Qan;I;;w$QYnsaFnd_{i7jEEQJNqi$b(p>F;)m_ zk78e_3!6dXwkFf%eIbio&k47Sol|MYg|=T8D7O`nxh(HwANjiLT!HJVO?*tAKjhL? zz?ST(d1PKG52V^vxQ~_&HP^V+ZKphdE9XW3Sb59uEHI7hrq^W+ZE=>_lL#DzE4}j72rpC*5$gx zu=OIa?&EyQv~c&A4{H{S2!VWoZRCWNwh7cPd5*xZn_F+lhX)gKIu}M@XufkzsLwr& zOrw3tbm;B7aRE)XHx3$%5W5j%*ojh1xVd|_NwI}ltm9S<%DthS`&de{Bq z;HD0b2%IhM-%lML6L@jk_7D$~y(tpbWIPE_9lj*+a0W~;RENSC?2S73mkD!t%=ifOlaX z?`rTK+{f=Wa2(+EeY}2v$5^BLuF&}56pcGmG%mx(_{|bLfKUAOQ}_(801xq4$LAE9 Z@B|v@MH4>9>n1+0;oTRog`*{C{s(&GAOipZ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportBatchIntegrationTest.class deleted file mode 100644 index a0f474df609d1a7530470409a7b5a4feec9986ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5723 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ODataV2FunctionImportIntegrationTest$TestEntity.class deleted file mode 100644 index 16e69b30290e402b7de64749fab9e6a44a40d2e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7513 zcmeHMTW{P%6h2PV?uGP1(jsUJOiO`;iw))8l$17?CS{XUO-KbiIre1NiS2Riag>1g z7yK6_D#3ey6ylrl?#7!swIk9n_&&ziXV7SCk8DN8Uf7;A z==8J*6KRKX&Gu7Xr-nfzeeNDl3>ucZ5jW`7-0o@LP7-Wxta$CvmT8RjU-z%yY#omV zCa2U*5)tq@kNd7cmmZ{$J>U!BwwHM8KEEA?LTcqfoO7)e9(%U${leWPJK)%Sa$Cu6 z(3#e~M1=Qa0XxWDER3|YH8fv!h&GhkWYAcfcP)@t@%izIoErK{zMP*-qbTM{^3aY$ zm_Vs)-S6P%w|8_c3F@nl8J2|$DYfXh?y5euqI*|7E4m?mr?J)W<$r}QAklquL_u+ zeOQw&(%U1%=!!w7ZhE07rwqC*uF_8yg7c9_sz}^z1QWU7bEK=*qLBAHfgsf*31%s5p`g4C_5_ zHRycKKFeL-0{~sr(>s>N4-GEXk_f}p_px|ld}6Im*NwAJo_!OILn=lT+%N@5rb<(0 zmZl24$z%(Xc?t7E*Nc(4{9WMI29J5@0O>Yj5n%i%#hS}ZN97^5;rTpiHjd{MCBja@ zy%GI=P|aaNL6!hKXfb3@D~AOMt&c<#}uWv<`}Q)x1=rPI-R z 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationIntegrationTest.class deleted file mode 100644 index 4d4595dfc7d8d83630ffae54f9f587bade661d7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9695 zcmeHNU31$+6uokjI(AdqCbSd^Whn(xK4d;xm?53cWZJa#)NL|}QwCl#8)@xqDy__F z73d$~k-xzV%)mQ8is7uBG;*TEs+(!_;0KSj*7ez|d(WP|yQ_cy`}1!iTBPr)RB>oI z)?UMi*fh5IHg9-Q*ob+r;RR9RH)8);!{>&FkuQQqWG@?=BIpU-Sms@+xRH@+DkHi& zy=()gN)ry9Pr(ANx{alcE#Vo5YRd}`7S~ssKP@`sG#xs7H&U_T$~@&kB2K}jHyygP zw$fbxq1k@8wANlAH{Pbu^FUmcUAT~F;n3y%_iwiV zHGcqt-0C1>{cHKj!y1>3@B2x3nLX+&60M;n0%K8lKz*ec%x24qc+e~yxanGMx@o`) zgZE<~!(OC~O@?(TC&Z(eMY`)E)bDZ8$FQzn%CI1rj)Cy^WXz#jP_hFdW?(Z2LDz|A zBF*}`Hh05HSfYH6NEST0K%=YEHM9-Fqc?l&U>s?xGw>P}?X%YsWo{WGGWA5h;{=7)jL~ognqvk&|e)EP4eE%7mD!=cB50 z7G-a22a)QISS@7$OO^SIUuBfolda|pR#pXy{v)Q}OpR8jS-Lz;=jm;SF3z>=-asSW zp6n03dAq}Ru7&02lO)^_x^1gIhh|!l$AhOkHU7?z6Lyj4&`RsLcTTbAShu@f4}@6= z0*9`wB+4!&nrd6d5)LicO~Q0r5&fTT*;#FVimo}8-->KLo@~MkWHK{g#`ST2eOy8z zIb!Akh4=WJj(F_2cNalwBSkz#A2}1U{LC#VRmF5DJp&l;QS2O)%a z-1qC*@lx`@xsxfd-9J!)mMHTXzsgqu;^m8+Di4P$w#56)ac0V|ss&5D0&AFJ(u;-O zYLsYC+{Y^$4qZL;1S);;0oJYE6IrYgC%5ejGgbNuE7pB8YL&jhkNeKms&otQut1C) zntm*CuBp0hoJYjO2*2aKn+bY{Ch0Uj(`cU^XwT3abPo3>@i|Wy zU^#=Un?Q5$UH^+_f1J7a2fg(VS{3}8Qgp)IE4ZHSThY7p9$G|Kfl#6MabFyOoRfaRIbXa?qA@(QXt#`!okl=Ac#Rb402_U(mPso41A&|5oFeHTOqt;e7PGrkSawhbL z(V2FpeeaLz^vK>NrrBUET^Jtvuq#`>Iy$<#AJYBn@8A9)qGh_)qA`y;T1BDZ+Jw7o zpM{Z3L(TFq60(Rx9q)xPGc1)c7ooI|;SLu$SK$NJO)_Q@nLWx9vk8F9ONBNvt9~PNOQofq)Cpzf} zzL;gwl(TA&F4Tt$axfkB$8CT5k7#~mENI?6hNAJup zYsI6H#*Tb0tA4)j#;{sm&eko2NPV zEkIxw8dMAQBW79q9q#u*0)N%`iT0B$P84(@p5oIsS3HX_!?uzs-k%is7W+XYnAZMw zBDj8&v&0+|TLL+Y_E?wum$32xwgE!0jR3Y0JhzRYw2h#$jZ1zkx%TavQ)Z$a-#|6| zAjd-Ma4@{U8eUK;%o)7WL6YWD8M_%lH!(X!XMmRfm+Wats%`)q;>zqcsc8t&a@z`% zh$hU}BX|&$ZdEzrQ5Sn*^DTp6x4;3U>G>?(J?#XD+9t{Qv_(uAyOqlJd)if7L1Q*f;jur^& zhw86V==_4smPHox?Ig1q5*veORtTE{o=XS*J)9`)7fAUmvq`4{t_iiLZdW5$jLC0< zT<;ju!136X))(?N3fAG^y1|)~coN*5R`&>V2 znLSVvo?xCjpp9V05D9Dq$k|g~#hGZHf;=L_o)gbKi!XI|&!KzaP1c553h+8)WMNwPS~W zv|tkS3nnG88x`u!`Kn#0DgxS)v-jsT(Hez~s>D!)G^D)n{{9$tOJb(v*IyZCtd-_^1QOu^X@ zxEJeXv-VowX|7Mwr=IuBp}6G`Nu6xb`5e{Yrp`DcI33lupn;ZE(+FmCI**~i*|GIi zkGAF)UV}NpI6m{};>(Q#g-?$rP46D)(HT&&N(jqBZ)Fm&%rmvl`}T+9-SKyAT5+f)2kT@w`DHlxU?JZE_vA(-o-=T_hB$QMC7Hj3(#=+7{ky+eSN%-$|Orw|eip=)Hzf z9@_TrH1qS^sb6XCPrQxMS^V447V&-jPghcOjy@>+Cg?+qv}kQepD$aY3us#&L_fwu zAAZb!Ea5*H0RL8b_Y$KU3eGjXPHnq=$SuKWwpriZZ>-iI@2s>o7|j_YlBNh8 zud~GH>Zw*cyDbp;Y z%0QM7-|t2s>G6hl?1WC+Bo2p)jMFX=|BZhfcFodeZOoJR;5(lzTK;G0z>4mSE znsn`IC01GvQ7l~!Q2}O!aZa9}S9(npK$`-`#1|$rtJ2%_&Mdu4?=iZv>>zsHV|05V z?H_S8T-=A@4aehKQLoK|mR!!#Q+&&G_f3QXJST%UJ6y!^V-yG(-Gb+j94A>EW%_{8 z;dERl4xN*yD?@14L8NWHfK`Y^avM`$I}^Ce^a-Q&$+MK}qu2~e;L%cf;O3Z02bo~= z?C5jF|Eu6P$Y2e-OuuII*U57!y4VM4VY1CCGG9z4L8=bZLNdDhY@XdQ10)&b)>OhR z(+Zp zPZ1Rlc)&diar7YYdw6~l;hnANmQ3~9f#dS9TKWFC*c^^kH45t9-7axQ;HC=#InJp5In}_*g2v77;jZ;K|i_l*{3zGCO zLITZ`p)jHJ=>s!F%sT1i>h>jA0KVUGdEV@moP{Y7Wr_e!k1Q?;^%d7$2|A(L{{@jH z{2)(^ppNZ#qW(CAE-cDy8F|g;$awBd(-V0aiSBb~i`Px$;mLcj0*3nD zNMy>$5x0obrfpLIve5FsAm)TMazUg56DUAZB%q`PlN;C-+w3D9B54)Fy#6a_bKqfTEA>*-lhZi`YeUOU?v)(3l8)eKyY zS>IcT?~C#FE0m5eH!D3S4AB&e_j55x!5&rI08u=0CLdEaxojEBE=mshI-0DW*ien{ zU2dY=HmRY8p0Y@iGg>gI2F&&`4+6*LOJzd)+byuKEf7URsV+kU-SVa%1r}e&HlNXJ z8U8B;VpKPVLG-!0I~8NpGrnTTf)s76o5q=>iqTuf*}RD1sxdG-9_~xoQFi3I>o^Llixr80;eBkH~I?#dDJ;l*M!$Uh-MOPk{=ueCq zQ#&GnAqH`wL^#Q5PYrq4n;-s36b@w#SSBMSI$-o@`kYl1lE+!eL5_MZkptrlov1`d zj6O!vDR$x}3&7O3JxoUWWlf#2DHD~*A3J!9qbIDm8OK!F!jwl|TR__ILqT^4O8|#~ zS7@Ft;}^#!c$%Ry-JmL-XOibv=&GW^NfYjKbY0PokKLeGK|iOyzd1%fL$`+X*TC;K zXnu%mUftiIAAxWD-H&OOD)bZ5fF#kYxZlD3EnIhTy`!YX_)~iOi@BeDLyP~w=nVZF zzfyXluSr)Ugr^$)BEgaG1{fa#LsG^V?`L5AQ(?@;RP;;w5Y&u5DuDKJ2HL+e(LSYL zL5_IVpAEH@En^nyzcW#nhB8XcNgZb+E@#O3O(xDiwRmBx<1sZ^xkz$HSLq*{`4S2;BPYpZqVn6z|vL{ zbxgYtF>qG9Mh5)T4ERg5HH05H|Il#R&{>Adf@ZW$JBi`ak`II(t*xSnCHfsEJ43t4 zdT1(to5zZ7VSEqQKCVZ&evj*qxW2?i^cAjo=#bH4G7&f0_-EtJ@!Y|^kN2~PYh5}d N7jzyy!N?3f{tkp_A3^{C diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest$CustomerField.class deleted file mode 100644 index 78d19083703889184cecab32b502b494a08c7ee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5654 zcmeHLTW{Mo6h2B*J9g8x^_F&*Zez1$ZMX^wM5}D*UN~oo&N|=b0yNw1+WK2g-X`f4KxsuN$w_POga4k1hrA%*f zCXyyh2XMQfDX8p6PkKizwoPaR@blzZ?lXbZX-}{K8p|9dS_xooVQ0vH-)b)VON$2q zOs}CjO}G)jt>N zPQkELki=9$WL#COGk;9=@Db8Q!_$(n|ET}Q$kPBws zC6|ODV<$jH<73)qWF9pS(GgG}bc7HcA$;QqVc`g2=?L>AQOpphb4qPIAQnx-PBK)q zN)ER?bY2z~2JsT~B32(g2sW7WD*`j9P zij?Pj-!qF2mv3%ao2{d8 zXJ?ig886kes>u5Ydd$#TY=dGQKA$D4!h3AgJA=4{T_diSt;_JNq2$5Lo6gDq=k3+5|XWp1rF6;bEq|TnKmKt`Yl^{5LK$kE}H&Nw54rCe61b$uTK;Y=8&E zr=nB^bMCmB(Cp%JQbl5oui5y6B;Amf*ZOrmC9mlBu*RizvZ|ODv&m7K9J{sR)A8+T zzuwmMh#y&9rrKwkQ^9{>$sSGX^1~Pz&@ zM$R=lDpAyiHr$$pCd^=yWd{#vdwJSpx?6T=Iy)++;((%;VEs5-oVEjQ0(h}A`R-6* zj->$Zj`GF+%^C0bs+cZ!3*OzO6=dJuSGkVaCN_5hxOahzxD*yZYoGVAvzBW_{k>ei z|ESW*Do@EvD?63FUcLeamWHxl0rjEdp=IW_rHdYp-@)i(oN21#!D#@q+fp)JBsON6 z@MQqo6Z4J$ZjOXx0G-$Kp=?cf5WtO!p_}l{NL#ZEN|bcB4B0L^6?*Z^9)4?VDP!RQ z1aJ-i*Rkgs;15FruPvNy;`$Vh?cbsOqBHv|eDo*IuEA}*HW1_Rcknt}+_#|vALHzk zk(D`|HQ+8@r{ErZT8y8;=QsjMzwz9U>r`nUZr%j~3CIstfq=n(13l+~?)S}wJQyP5%K3JKB%uAs`vz;*Rj3X~tZ$bA5BGj!j diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/PaginationUnitTest.class deleted file mode 100644 index d3849b8197c8bc75010aba30e81876467a97e8bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22714 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$RelatedEntity.class deleted file mode 100644 index 84e5573f8f5243f9abcf1e806bd1ac44aa939601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8992 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TestEntityLink.class deleted file mode 100644 index 07c10b7c3dc1a4888a7e5dd7cba4bc6757acb973..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5912 zcmeHLTW{Mo6h2C`*w(UMvZmX*8<(}f_9DF}fq^cKkqk~+EN+TD(h_YmmPnPPjCg-+ zKnHB^`z`wo!wx0KwvrUm0K374JeV@2b0N?5BmeyS?e74v4^L~b;=n7d0^cxg{4+YD zejsCC)5H%VnTEa&hki&6jb+FpU)sO^fJF&Y{s~hf9D z^+P$SLgyOB0CZQx%-@`;>%AA#jNEWbME|M%YJgOw`<_OQdtd8ZC z?`lme1d=u8S&B8cY)_R5Z36eGHcXwzrIuP+woBQMSlOO3?MtA<+FM2Sdnb!z+?7M` zIkxGt^-W0k-=s9s&qR1Y^}x2?Yx!Pd|2vYw(DwLi2#Aq{W-Zrki*SBL;PjrcDtJf`t=fiq8RP4H4lM2@CxBbg0aoJ9!nt^l@qnU!{OUo->srAXj#FrV8(X_M z&g~$^GfaejU@%?%&1n@qMszS~3dzsR?%ytgT5D_XUb`w!Qq zBNlQxiP{Bp$Nx`(nzp)7_ z(CCyRq{8{+<*FHQ?ZE5yQts$>9C%dJiU~B)++B{!-OdD|>@-okdm>X6uohmZIq>i@ z7O`i02O1~5j|YHMA<17C1#-JRb*ufbN~k~hcWxWFT>HD2g; zFrF}c>&JMCJCNGIH{ie>yn6@KM0Qe`v3p{ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/ServicePathTest$TransitiveEntity.class deleted file mode 100644 index 7c98fd44d8dadc9f178ba0c5f3b70278331a10ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7167 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex$TestVdmComplexBuilder.class deleted file mode 100644 index 8127755b698ffe8ddc000d2093e8ee862c06f742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8162 zcmeHMUsD`K5bq_x!39YmiGPxmP7w9bxT{8A#Ig#J5G+Dcf>@U2!>~KYP1xPJ%*;~6 z59C*{N~`qUui$sF?Ag7OJvg#`WX(e=53n~gJ-_}lJ<~lsfBo~@9{{ihpEY32gCC6! z11pRT9`QpSgenOP?*?J4vM4aoK@f4v6BUU#Q0`~Y5ph@OUHe|@H@?kMD-*Gi9!ewk zVzH8@%5p1JYCO1Eo|1LDS{U;^*D205ICk(J$Yt^inM(Vm2NzmPyB>_MqWu~$@4=;GW!JP;dJ3+3b9cT=Q3VwriikOh zWSnctx>}*?iTRv0r*}Gn*(Y7W9Dwhv=@-^yA$PW6dxG^re70ekG)$(E)M)Vd2@bV| z7HNna+FB*pew^W0#QZSk#;~@Gg?V0FoC-%nL;@9XigbB+!1o1P zKnwcl!KfH|u#X<>|L4Je?!kWH!3!)>!Z2sMgxj#g&@q@e>Y~BQaOe`g+a7*ycrtil zOPO?)w#AxdR>nh=K1F!s=L=f6!0E+(CVD4+n;R?iQBo<4Bi>hPKNeMDk4A}$Qw3OT zTz@5wSMtO;=FZX(db0tEp+dZ-QrR-BH< zSK11~ZJ{NP<Sy`tbwjp?9T-@ydAW0$5g=v{_#&LrAjfUKzqpIP=<2 zUg6dJ2LE&~*ivbV=|L^Nk{4+zkV+9&V^mD`gkJ4n(LzMI&$bG$yv+uK>tGCIZ49gK zQl8g61}U3On1femU>08U;99F(5Gra&uIzGY6GQ1%=9ure#K;w!RLJ99j$0Soz0Psl zkyuswsh;Z3@JblggdPu*@_*(zyIDSYP>AcfrH+E*5<}2)4IErNS#^8JHJ)vFtge*H zZ_UmAPFS9T1#j|iLivi|>!gENg1J3+q@&_*C7 zFs^HXC?=!O)G1HJvXEI-xdI8Oexpb;b|!2ftz?BgNHH z=Yw9=1iPEtD$}93i?2@}Tp3L9@&^|WW;RlMy~}f4VHyy4uvW_}9j1+g{CfV-fKLYA zEDIG-j{DLWd|}+dM>xzDr?&7-=$YH}fyRTlj5wNXqa>AAZaY=J2r^CMs@AXVwHcsCj z$L6LZwqU7;IE#Y6j~4Bl{QRgB@B!kC!R-O3_|UBuuAm1#d<4^Q2ixP=`WRisQTzV_ DWF>P7 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/TestVdmComplex.class deleted file mode 100644 index da008aa341580d823b8f9c66f3fe3491c7a60dda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8496 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/UncheckedFilterExpressionTest$MyEntity.class deleted file mode 100644 index 011bfb2a69ec16b2805c672ad605db15c09d9b73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5131 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 YRrmzIeSzZ=`tvD#1~vE+M>A0U7ZxchBLDyZ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityTest.class deleted file mode 100644 index ebfd189c066a70a21a10798ecc558297c8407c4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12915 zcmeHNTXWk)6h7;wiQ}eCNn6^cT$WOb(-xURfzm6k<4n!uHjNX82PUIPn^>tXMp|c@ zXMO?yfEj)SGcW`1Jo0CFV>qi-94V1wIa#Nj)(>{P(dyfC&VJ|Yp0oS+f4~0;09BYC zfgFM9z&A>q1-!JWZ|Nn&b4r2UEE%@fGE0H^ykzQJcRZ8XB~Sb;HJIIGerd&YsxG(q zb{*dxfgu8CLjYTMH%bd@o6O(@F4wC|_0{R>OnG^>zPeO>wo4_P#*i%Is6lo3UPtzK2?Iiv=jf@B<5R9uYWEoLV6;R6+Ge;2eSR zU6HH4@A-MSKrY=~c6}Cj_7*cKny+cu;kdNvduujxf_tz2)e;~d3g zIIs|)sTHkF#7sIeZd%U%a(VWgLc-x4#v7hVHyD?Z zvf|k`(i~$FTG(5c0ii9N2wR%?lAWm|jO*=)gj0a5#&E)1j#dk&E(|DgkizJ^=ejN1 z?jn0e6)Ry>+3y4KDNAZt$Kcintuo4ch$)$t%gGY+w=9D#>b%hh@iWQwgrS46a@V;# zEd3)*_q?7$l#FgDIGXZy2X5$`>qevQMTRa4lHQ_wgxOtvQ$mkeAVR8la+ShpN4V^A zqL;?FFyr|%3|FVLP?$lUq;IIh&BUkV;ITc^9m9f4Dzxx1O{t+r$he%#H)G{}69ERuEt@L2el&rcB+U?f6R=QYNAGhR%x# zbuw>uk_s@s!u$Z!h?P!!oUf>gLcv$Z_yw4Ni(@bjm+(-dxc@jroIQ-s zVsUl8<*YHkE)p(rZsHlby%Hs0(LaZH!wLw@%&Iue!4r^^XsP8Uo|8Ol;SR%LHFwJj zEF8ZqPAkF^ISzMfwq3irpN9_!{FC7*r2R_AcoCOO%e5A`+NN zp^*$m$SFMbT(>sxo3j@@4@HvuK@FD%6}dr0Zs%Y*R-q?wts@;{nNc2Y67sJaCdT&N zd!w3k%t3+gz+|9Z_7E#b?4U4_7pM(c*Hbux=TtIkE$Y6Gs}xou@}QAirdtr**Sa3RsiKmyThdlu z=ur-+T;JFl*xvWaRqCLct%%I+FrA3YsIh8D1v>YTD)rDc7TOfZ7(;snX+vOA1h1&1 zhj(ydkz^eRhWbq^cH^yRZB*bQH8+87~_(3`-({i+a6}e+Al&P?jAunu2ySM9X1cfU60N5Ai?yCAyY^aXUpK zfa@91K2AYi_Gt>5k&5j}1p@yR3j1}gY_NId%=b$miV diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmEntityVersionIdentifierTest$TestEntity.class deleted file mode 100644 index 353f6ca254db913bad007ec752f5f6366685c1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8660 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 V+H^n;xzr_}LOh$n*PqhSe*jEZVT1qx diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/ODatav2BatchConnectionTest.class deleted file mode 100644 index dc481c5d97015a80762a96f942b7126a08a39bd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9320 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityCreate.class deleted file mode 100644 index 1622169903914ab5e3a7303028ebc42b79d0e30e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5759 zcmeHLUvt|;5Z?=_9edKI34{VIaD)H}f20?lNT)NYNtz69%EW0H-Z)EV`x@y^oKBwl z6YwG6doTkt@Xm)~*pnP8$8IFIcA*0gwsfbx-M!!L?e6U#fBy0t0PMkMRahYKU93IV z@YuMISf9C`3f-7Rt{14pcVqv|^_gLz@_FDY`|9?15OM9cnelq=2`)VK!#!z)8Qir~ zn|3N2H#BF4S7DLB^^|$QWY>Mre#AXPpu*Ed1Xh|OTy_ZDJWlXi$PZ;-#G)PWT`84e zMkpB*SZdzm#&C^_pGa*bQKWesf5WsCvK!;#D}5FuoWNoO zcUOfK)Z_{Mp4Lh)!75q(@u9?pD(G{cB07->QblN_6>1j7U(kB(q{pclM4Va%j@_n{ zZIgP;s!rRSo-=Xip`jwCLi$1@62SmJb-3ozLkS(NLL7ge;5VN-Uclm*c0|DA7b(T( zDA+0xG4G6ZIo-gWJLngv5c-9Ke&PJ>7f$LIPUaU*>K7Z-S3IV6=b?29Y7ij)IKu7b znWF1C*7ckewkbtqc!)4k+GMy(ZYJP8PL~)F{O1TCJE=>N&MKVs#71du?5ZFS~k5xmG`L*nCMINa6UxPd9a=)^NhPh4#y$%GHo zD9X|hU9XjAmtU18w|z_)(j252@GgsmS0Ji{Hcv$&FT7$=Mp{_@K=L6j;`X{=SXR1>c7IZZ`ZE>3~RrqH0$_p zS6D8uulOn%n1luz^p2 c_wi@-B(Mn|qVAvIxPsq5f^GN|-xpx>FQ4tHBLDyZ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityDelete.class deleted file mode 100644 index 4a33fa43a15ddabc6d8a427c6da5b1e842551fba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5759 zcmeHLUvt|;5Z?=_9edKI34{VIaD)H}f20?lNT)NYNtz69%EW0H-Z)EV`x@y^oKBwl zl@~q)AA}j0fp!=B_&Id&ttwM!XzuqB=LcK3ewyWQ3P@#inU0l*%7R)qxu-^JQ< z4Udici1nH4snCsChDxU|gvbS!J2NBn9n;Ea?p5Vk&Kirc>n895; zv}uR3=P!A{4X?r?f$ORAfXS}=p#6w@hCqdP z!;DZeCa~1J$Bp3{1uM6O6y^?rwXF+EwoeJ%YCf^rN}@>fIR1udDP%Xs$yfR;NH~GT z2Cl9OE9l7+`8}G!`qM*+6IIaXK1FmQ5u}dLNGtR#jK84u+DVU7Gl)2~3LLvk zCtD`UaT*W7-h`k6)w} zpQB;xK*YQ=*5z~qSMDG$&>`f7gS>G5=7p2;!pV5yq`cUmzTz>pD-SIxs6l}E;|Q0V zSBlhgEcKiewkb_yc!)4k+GMy(ZYJP8E|(Y){O1H8J1G-0?vAr^45ex0mF_wWse+qa zpm`pWL*5jb{$GsyBfI>M01~LMt z!f9laMEGB8)ffIHYWqwJhWRjKm|?n$!8lEcs)NxV^BsnNn}9Kg(=dtCl*ni&k)BQK zEWmt-IlaR?RB@!FOoB-eVzca_2VHG)66Xvx~Ly)}ctlFNT z@=}rMS-pK2Vf{EuBF)v?hUPE1w=K*M0Y9~0G=82H`@;ZRz>89cqm zsK5p;%rf(ZrztS2U4FLzr3gMJxu%H`)5sQYeJQ+4#r$$qdxn!Y&4NoU=jB6Xe&s_Q zuEDFzP=nV9tZ(JpJ!!$bW4k~rO(FSF61KTMvBe8Ei<-(~!RfGK9S$zqB9%b9Iq!ZL zcAjcj9A}^qSl!yb(7Lh?W?w$;JubP%%;WDY!P^A(FK-#@|U3cmjcw&7E}Ux3ZO0C*Rv0{{R3 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestEntityRead.class deleted file mode 100644 index d219218734972d4cf68866d2385bc06f0ab80269..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5837 zcmeHLUvt|;5Z?<)oVae&rVRyJ=rs+H@JD*#iDWvHnuKI<6DCg6;f3KWo$YI+J8?RB zYQ72|gc+EDcRmZBfniTFjhwiZT;dXjK3KXt@9pmGZ*Od=e zH9R)%6V_v{r$RSok?RF2@!i-zc70}8sC*u{$`0Kw49HW;1g7h#W(8^l7Eb85v{rf!u8?a#K9aam z1wHOlJZ2<#?)h0BiiEhZzc}iHB`h@NMC3? zRM5x2+FWz#p@gS1HBkDA+0xG4GglI92ApSJM{I{oYZV%ipcCC!boY8*_DDcYfv^}pi%bJQcN%bGmE{B1!#N7mY-0=iPFJur&P-Cb-5mngSJ!f?r;A87 z-xM9mOrlZam9rEZjUmrjzi+oR2K6eAZ{%3+_qf(rzOT>0y9DmOxS@spECSW0O0>tf zvGGLU=8&YcpiVnB1pa8`J>C{K8+_To?Ny(eqcWGC8`2C4--hKrNu9^0_ zeFC*zDY;I8<8cL62<)G;7opc!5Hh#P8l)d$gY>lZE3ii3@(^4V_;{$XIqD zzw7v{WmIqjmT)!=Z^BL7`4*0+;BBbjyNaWC@O>*CpXPjf1m_<{aGr(tQl57PyeZtp hIlwahs;C2jd+zwwdvI?g=hD^}{`Bgc;nm zQ=4`w8$XP20k6U$f$J&rfXS}=p#6w@hCqdP z!;DZeCa~1J$Bp3{2`jgS6y^?rwXF+GwoeJ%YCe(LN}@>fIR1udDP%Xs#aH?)NH~GT z2JWs3E2zm6`aP|cUV>G!`qM*+3sunPK1FmQ5u}RHNGsGVjK84u+DVU7Gl)2~3>>>n zC)+0Vm{py&IX!3M&_hE-OojA?MkIm(e(G?|rH2waT7@|NKEZE3b-aMZG3|(e$1hTf z&rz^dAY$Gb>vFn*J9p48P$BdS2mQkN+b^8dFPzLToYXHisIPcT?ao8%6x1L<{BeZa z%`-*UbFAw*DQr`U$nX$hq_oL!m)uOidz>yYBKXe{K6X-1%zSs;E5}fphF$%e$o!*IB(yKw_kfYFI(l%BZSMv@61 zs8N)qA-Y~G&n~|zO>X;`Fr+z1GvHko3$H*_32mN=L|%ByXiQoK!gh@cg?A_o@A66D zjTC2YBFJaqfS}@FqnTHvsyBLhql&Crw{W0HR__qldb2tr?I8BL+b`t5? zw9W#|hnUkl%tIDOO3Ead6r=NdC`Xmrtiwd+Vb%vf=wdEb}Vl7!9IB%I_&ajFcs|oYmr*V)x>o3tf zW|pQY6`7t@+w5*ILn!8|Z9{Xs$-Kg8BX3b8!ok-mk*Ws zl@E2e2Cptd4PGO#zLjtHqy_Vi?E&hya-F)18Tyl+>$KP3kw+ZZD-iorFs4{7gK)t0B?eTr= zD-pOc;(}?toHk@o&z9&)W)h85KQ!~sou%*F^9Sn!Hg^!c^+W05EfAxvy^9G1NPE2j zf#pMqXfw~|ZJ%fN#Z?e5VzXTNjK?$P(#-#`6AL`!tTqzQxi zk#rr!Bjwy-J?6M!$B9_ias4p%oXETDcucWQ=yBf(^`q0~ewRyUiz&D5tmDN^ud@^= zq59YK)OkI%90#rvVNmaML#b+^@AG85#-rF*D@t{5aAne@K}Qm+J_}mTt*twdGH9yD z{Fp}u9cbKS-Fbs%nxYjj6-#c=1wFUWc<@$JNfET>b2F))`Kx&I9wMpf6!(Cu z5qJ;F#Y*`9Il+5&l8I^k4rk>MO5@0fvSl-*3W8jqc|RnxyeTsMzZmxhqWnw%@x#tm zc-O{-tGFx*Sth)e9fymu71eOFDH*#e9R0RFNk*u(RA{l}h_1|(SC@Y)X(yJ^3Iu8; z={c^kNVo;2N_g`~B=W}7JG8*qq6$00tp|#?MnaocIF4)_3-7iJd*~QOVV) z<4WdDuEq^uIU56gXMxxIB|EUQ{x`h8hkm3@Ym@7~S@&sssCus|1bujcSEXv`kzHH9 zc$#rHN#$>u4AAXI1%~sHS>p_y^iWTj*FH{ylw3_pC z4PBPtbLOgz;rS8Nou<_)ja9H_y3@hn{KFjEZ6;X+D4^hC z`ZU5;jwQ>{gomMN=h$9_C6IRmj9{7tP<+TuuBF|rpRP7G*6P@@rZhLC$QJa5nqe$m zz6>78pwj~>m-NR80b|hpMwzp6;VJNP>4`Hy(9SoqjVBRZ*cn|-sMVA8z{Qs#Lao2g z1eZ*@?Y=>?^&sFfVODu$(uW4!+GA1?nWgdE30MKgd>Q#v24j;xHRxb2VNLoR$vzlZ zldgcl)%k7G0vJoAn@ONwr&6Fy`WlnS2$QZ2@Vf~dCS4!kyVG}o*iC_tw4k*PIumI; zy^77PgpnePpbb<#NLxBUQ}}OUG(ppNo}?M7U^SgNNV8a}VC@k8zl`5u{4QhN(DOgj zk?)QjyH6*6#b|?2efUJU`meKSr3Dr1SL7E(GuHni&s*KSyeQkE#Ib$w2*Uq{caV zAHF`UaeOF@QHW2`1;95!A7q$cBpYKQ2fqq{Hs}(4guGe8_!MS7rcdZI`hw=^OZtiy N;aH89u{J@M{sEXiRdn^STx4yVWK=&i$uE* zd5gPV9J&c_x?T{czMJ@uT%T(m#=Z#L*xb51B4`Tb*17g}+zm|J^2584mU{QL8Jagk zD{16u8Ap|{87sZ2b@M@-Xp6=foy^n(JZiZ2>koye8BMf!kcx!SvFbhETx2v=lZ}Y$ zR0&4c&Dc`)>8zSoGHNUqMsl3RTbMbqBqOPBF}k`?5a^xK9=W!~Xns9~^`WRlEt$xA zARchVL!pIQj-ps|;|FY;s;ibFjAoZ_8tJ6^z(#NL*HNRCpAFQC{M(B$4D-VpX2Ja@n(7z8FcV3M=P zBqw8%lQYT5m^5emu}Ey=d1z>4YXnFUwz8V(0lkmLc#hauVK%^=nPiwVCg%l}G1f;g z#+E!NWo_a0_I9|e8c7-3w^pgABRt{HC*&@A~)y&&G zk=_7RLs;|NNEDUl1#W<{WgUmot3+Bf`i8a$IErkVO8>x3T-9-eLPk2~1Fb?J!NUlF04t_R)d|sE1(0HunJ0uFV7d9Zo9CTX_d1S=59c zHG#wF=sGtIu==oMyFA zM~osnIQ2utd#F8sII6wC${WSuq1<`-qH=i0hZ3EjGt)FhXBnOA(<3q+q1md8#9A8G zh1xKO7e=S6vB!h0_KCGUIBpK9jDqUXbS;Oqov)!+*rq|g5^8J=j3Mcx-NqUFT<^6kHXmP<*@mP4N7UuH_JsV6~sNTVi zL~jp!>tfOFoTxCoR9!=p&4e8fGjMjV9S2`6cm=lN&5itS=*HkIkUEuZ4edQfXS*V_ zr+>!FD)*kv7Z$3U>lN5)x|o|vwqF=aYjLVPv4U0`qYGW3%|0Qr9u=b})qZPy=_pzA zOx~+)XHCQOTqW}Gfs8_AK2xPoTzNaYjHWA5Bvi&Gky!L0qx%O;b4C|U%sNSVwpIKI zqvHj#So9fE)-RG4eF3UVUNSVe!EYyuj3>)0^p7){`>g{Lu^#64lpDdGM&?q=^zj{J5nGP~CYnZcRMK<*&UwGoZ^7HgZ|fgjr!exnckIK4~rdxl@x zQ!|?3KlMd^kFFvF9m7XMtoKD+plgLtbP!)46tWNe1ig>&jnVaX7C)dHcqVf2D1zUx`XHQ82K1n`JBF_o3uo?=vx?e8~@BP2=3pCR8a8% diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleEntityResultHttpGet.class deleted file mode 100644 index b91e082dbd13c048c379ae52b19e32206ef4e478..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6684 zcmeHLZFAE`5MH?fB_M+q|=WV>0%$~#XX%oWIFvN z{UM!cXUg~fs7{}g>>$Sm$u!|*+7C{2cdKXjp547$?O%WY{wERLq1z@^4Ei~iuA_LY zoQJH-95)J_m~|Z2j}p&`y+@A66bmAc`%a`Eoi_J7Tsj*}xou|^FRpvRolpt2bxTiO z&{KDl&{ZM|>p>@yswu*j&oh}79w)w9QmXSES0+subU4-Pv#{md-+0JfWzhaE^AjE$ zw6AfGb>>AeDbs<9k}VdDEhJf72Xzssc*9>?@V z4OE(R#Gsj{JGdidBq!;ZLFd*&$>YfHa?gS*I>Jw}t&WVKEVyn}t8|!7Ow&m^1)df} zDAWyuPR!=C_4t@uhmM8_`En9$aJk9=V9?P<xFkntXJHG=P9xkL&7KPULuP9rhP{o$w_Vrd-tNVaT-P=S*RkoQ8e7dJ(!{}CKGw(+5KCfY*9slaOuIjr&l7gHhWba^Kl;{A~u=Jt5dy_ zr(nP3q*A}t+PIQ=ldEwZSWd@4QCDE~UdayZZ0mczwTpIS)ozpfxW9Fcw~Nrbl_4nL z3#=-YL(i<*^2yUoI%z2Xz+{Negt$O)-ZLwlp$Z?W3G?XV1V|qGHr%6j(>0||rWdt# zt%LgUjj^^S`D<=%_CdU^KXSc%RP;Za3xcjxa+DH!nTfc>;&!pVTIB143m4Dc($f&Q z)t;Z*ZzzNRGPuS$BgT;}c6}+nOP%@ULhS`IZ=4O6+Rp38%HEX^XvXo!+)WhMMLjwR z2A!F0+{S}IZx0ziKewafW>B@UjCPtXW6&Quh%$A%^MihsiKZP0#LAWIsdQIx21xzG zb{U-{wCe^MfSK>KN;kbswhcZt+gMwv!%*G9T$7@=0Lf}IN~Fv0qWfdesewRAtJ<_} zV$hRDQGPDG(k|Q%59}D~j_Qee=;FH$=coR<5?nIrwzmwLu7@F)X+xFACVgVi{atP+ zqW7zL?K`fpx%`dUyVk_484Qr=}d~=rPCM@ z`pNG>>U}&NtYAq)W`Fyj+Nccl%rGcqhe5$7or5`jGtUD-o2iwU#t!((HvA}?&yIkf zpbzN6HX9eW)r`l+rIE-V(nnyUZ}@nKS4Sew(p-*;KH>{dacutZI%E4 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestFunctionImportSingleResultHttpGet.class deleted file mode 100644 index 8bc2e3a6b39c13d6398beaece6cb4c18f74e1433..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6662 zcmeHLTX)+;5FVK{O5CLBl>+6WmX@ZJO586^Xi43c_~52JaS{%X8);*2tBbo@ndZ0f z6L{c}bKn3kJi!}3h{Ly%?aGZ>*(K@az=LIJcl6EdH#57V`Qz`OekG!7bk(GaK}jrK zNAXxWw^^4tZWK5%>o~3-C7u&|cN~u?7DOKRok-t0ZSHrtbT*lC+s+zZ-0*^Hp%QBQ ziXJ+xhps1~t3(vmgH9w>Q-m#_uktwY)v{8ZFS#;l!k~kxMxTW(=jP^Z?ka;OyUb5` zY|y^O4c1vOXu2s{AybLu2A$Vqi;eqlHI)SPLT)6(S-6BZ545<-?5g=J2IP_Xb2-U$ z5MuK?M;FiMkN23?21@y`g|!2BgOPW0YBYXiNY}PeMlb70o?3zDTT)| zJW&IWCLJJmA{_@+ ziy{>2l0nDjbJ}|5p4)(qh6wpe5^Qq0#sFZ@;YQ>#e}hS(pL>H7sx4stw~eO~2w>%G zf&IlRI{lynZZXLMuDHy3Vv61{4t@7%Dr0atz1HTI+U{^m`@+^?XlLPLxlB9O+T>Q> zAGTFjmWVA8dO{*n{cTKbamhm$&f1bufakjj<~+-Gb-Y}D#1^JGeFpoY?faIaC!aZs?T{F_o z^tjfpcThh*H`dl9f5xrNK8!c?C$E=}ivH(wNzjdy93u%m$wXXcal2e!E%Noog@b2r z>1ha@YLCwC=aj*J8C>J+5#z{~yM83TN80m83$@3{ym1yh(sG_XR`#xZKx>ZQ<8GqR z611`o5SR0qu;bgxWw@xUQwE@XEj zw+ZKf)ZcGc&`d(}Zm1(T&_C;@=gGk4_8bjozaB|GN2ktRA}fjKg-RKc)niOuFrDgJ$Ys$Yt71<*`Zc z8gz4)8;J&;(H!=j^oXAF2L|oW#gR!LA=ZZ7W6~!Ey?{_L=`(|@G7p%9rB7ybnDjYb zh0jg8Jk-HW7jM$iP)9p^2mIU+_Z#O25QD*yB;-7_x^K>ppMIZ4osQ9Y@ev;k>eHA+2 u3ypW^0-lL%{Hh?@Acx*V2wlPRQ3TQZ2$PTLQ@TitbO~mCK{d=&$oU6~{AMix diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntityChangeset.class deleted file mode 100644 index 0ef542cfee5dce993de7c88f7b5ef06aea6cd9ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6382 zcmeHL-E!MR6h0e5?3k3ap=p5v6`_=bAL)iG(&N-hyY##Vqn4Nqy8 zd26V-k4{H{(CI!p-T%w!zH_=ia=P!FzUD=eY0qk&SdV)K4aA=IQQa&kdegUYK)wvhaoiNK z{-2CR6JGvT3=+$vEl>T&%owJMTs9^?8mGf8)zx(=v{7kjWd7ealjOj9Mtanx=-Og= zxa?o9a_z(^6K2W2}c55gi&L z#DNqdjq`GJR)=y_&Tpe0qsh#)4lKuGqMs@7(q++3Xg2(s4QD9FsCR3!80)un@(iJ8 zwLzN})v5P#@6K2t;36L!Gm*^fdOV^aj zOwTLrlRoy3FO9Ty#a?h}n;3{OJEmBmqHI*`j1z1@(3M&orHHO_A?{M$E9O_1u}xf9 zWiHn|4USdq;d&f6xHKyTI~rgMf} zn*=J@BDWmVll6_OV|(W)#Zg?s_QZf|jt1GX6Dl_sK>ZgR^3e68+@5V!MK zHtnXGGeuR{8xmOB6N0I_9epxgfsY6r&Uo%i;MRl#GBfCp3EVBPdIdfuaBTtrh2H15 zxfR_Oj)8FS$Rueb1e8;@s*_P-J0n>mQ}97KS(;ZCkh@8lqS zm5cD+6bOg82zRGIXyqU*pq;Jt-iI}J{Q&Oa72rcWYN#E7`>>AH;xl|+K)Mb11bn=o HgZqC1d~jIT diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/TestVdmEntityBatch$TestVdmEntitySelectable.class deleted file mode 100644 index 37fe72ef0369b077b476c4d545f27caf698b8972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 539 zcmb_ZOHKnZ41FoknSOxs6Bl4jR;-FxfQnkF&_yfuX~vqSq)9bNk$N>29DqY1Jj0@z zu89;ocKqyT`}^nf3%~%E2^s=Oa2jPPyMFQ^lU5% zT-U%=zS|Na(3+auskkXP>TbNBra%5;uq$^zuBGZf^aZX)PJ0(jh~(nXP@xLF*a~z< z&QbAXRR|OkbOk2=Je2*;c03cD@>urSCpZ&0u64jPUrY$b<+JUYf2upe6n_e76uTik=zYwnrGaO)h6(=hM>SW9bLtt`2#m8Ty5 zFlJx|zVns;hvB!9>m#;fyV#{27?1p<)ziE8y|=r6{PoLkM6^PAou)L}3WaS+9!l#8 zt1-(CDptsrF~4MO6`pWgYIH8-qUPFso5}KOAY$Ad{wx9kjgIG9ALONQ{nDaF=XWBIt?;a0 zb3?b_@g?66B$IC7hZ@c1*0_{h08uY-xm$KIn7_7=8?!#3pUQ0pz8`rW?oYHyx>e&s zxDLngbOsjE>4-*0TAHi~5r|njrqSACU+^&SYTPkk;i~J!Y8X`!K(ERjBb{onFl?r@ zGz#2kejx*c8M0pG=`_vLJ9Bh`PHHrB*Y#a_PotC9+GJ{2Z($FTco}rlpxo%O3PiBP$IzG+f_t1!k^SM<5ot;J%IE zEsCIm=VuY#IfiL_EDVjJ>+vw1dUaeJ4#rA=Dzl%m5;rbmQkh6w5D00@MA|Z&Y0He$ zmYJk2GfrEV4JY8Cq2^gdT?X>b4U_CNWiJG-I=k$Re?aTd4*@CU*C^>|yV2 zyhysRTrbu;gTqY8T2MnM79h>PlpeY`G+TGv>Jm{+)u5dGWl{nZqSxl=-NGGX#Xgw zc>|D^WKePKtS@Hw*Riu=ADQai8wz6Nms<95&NaE8UeLEZjH!PHLr zKHMY6>6)SJK1w*ZOZ&KrUhR#MHY50JE^Qtw6_4+!Ws1{o=ha4191wIvO^%_EUgbnw zXJL6Tzf9n%#D&T*S@YyvRHB3D_8V%!e;8b&ZKhYPirn0ccpJd`@G zpH(K;K4_HA2a&M(Dt03@I^AK+xI2!Q@z~AFyCt8=NC4~4T<^a5?$|fO#!ax_w%0vu zQJu~8SwrC6$*gZA)P+e==;fOta?n1=3Dxz0iC{> zsJ2Nz*P(4Pq|;rE>XXp+MQf9dx7%7(eusyVC)cH{qJ!0FK^bBwT%EqcR_^3QRibSN z>Imdqw`?z7>a?uUPm>oWsqM*x+-JG#-O}~N)@enf=v9fkPtdFV1nt1nXVBcqLybxk6{59H-(L}1Z*%VvzX2UeH}n7i diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/LocalDateTimeAdapterTest.class deleted file mode 100644 index f0194e1fc24a253d7138dc3e0c9d38547d8596ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4182 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 diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataCustomFieldAdapterTest.class deleted file mode 100644 index 6a3a6a18b492e0e297a4abb7984a0dfc7fcb2fc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10068 zcmeHN&2Jnv6o1}^>_6iSpLSSK8fVTg>%j$5pKBdV!36SNZo`HM_w==K4&tj`VrpN>g;1&$^mN zZeszTZp2E<&P~Ase?EQRM8qI$t@ zn;V?!?JlPVz%k05R5|rn7)njoIn6*Ex}>R4RD`~W(7s?Bt6Dtbp@$k;k?i2}S25Q4 z)bRqQ6m5xstH-Iu=cw2i&}H5|*5>pq_S`|9&>-Z=L7tqHCufHzC*jHI@pP8@k}GQZ z-eKC?pc-Ao?{~4o{jfL~95Wc4#BoC_Nqr(ZT^VUAqqc+I;*s98xcxDRCVdcckn&-~ zwCM0$c(E42K@c<~ZpJu02X)4bm9~`aV4!~nNG^4{f$&hFin~0*DIO{v#h#WCP3Q2; zMKfLF(B~}?nkkAkSY~m6Mm~^s=rTqnQ-e~Jg=Vy;z6wF?+gnK6z+IA+0cwn|H zIah{a{1$7KV^ka*{}*#nu~z?&V{`caz=_nL29py|hNA?I&(uxU>n6uC>5hNS9KB4| zF;`lNJL^1Znw>m>V|D4V;7)I6-+MWxH-sYaW&P2e9pSOiq{awka+coqoj`3TgHmNU zN#Jl_PDSAKY8;yUd@0-#N}%=g=BOt*971Eq>xR*}3cO6n-+7LZ4or9)>5PuRL^h1f zGEsro2>J6F{pR$vR((1b&U{uAIFb>cW*rqcOUPf(s86ZSorx7&ri-=8vAE~8yKnip zE@i-3Sb;YP`R*D2DgVU?D%NT+tprYHll3->6jvsrN!P6k%#hO5NYPfz%f(s^=BBzw zUfO?pyNB2#NM8w@&qmBL3-Ah{!~F+?rjENt)s!o75LDnTLVg&j@sn?Xe8LXt30%ks zUBH{lP$z+)(P}BK4YB3`B#Q_RAzzOaqs8WSu~vg4Lf_UZW8_@|3wtw?T+0v4sR&GG zqhLOY*mk3JI}#nC#TLgL?o?KnUIhNP@=R8_U~cd6vOE>)3k8_>W-@E{L{}|yjkn{P z5!h+F%5@xtYLzRzO;n9=RZ3mwTRb2zleJw;Zp~GurYWy;{NA9>lukAFjpUwa56&W4 zTa$6*@tgQT0Rj3D!kXx|1BV;s5cZ22*`3Poz@t0=$S%0LKyG5WIw? zF|0WO(^xX{r|@?-?r96|v=#T2p>SWd;GVPM(xGtASa2^{anB8f`?>}9vK9CIP`DQ? zxbs%ri$meQX~A8w;=Vl;?j;NE7gk(%DBO1}xZH~S-oA0A75Dvp<8E7VXZMZ!jTQHU zedB&>#r<&KxZhiGFC%VgK(w)qf*-AD9~DIV-GWwvk1_U@;0k<_#KIEH!HItXpW88+ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class b/datamodel/odata-core-apache-httpclient5/bin/src/test/java/com/sap/cloud/sdk/s4hana/datamodel/odata/adapter/ODataDateTimeStringCalendarConverterTest.class deleted file mode 100644 index 2d1bdef817f8ed0514a20a347e432115fa68c524..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7715 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 P*=|5%@GX5m2Y229d>3~_ diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt deleted file mode 100644 index 32772d9ba7..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithGet.txt +++ /dev/null @@ -1,10 +0,0 @@ ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 1 - -GET awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 -Accept: application/json - - ---batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt deleted file mode 100644 index 436ae71527..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPost.txt +++ /dev/null @@ -1,17 +0,0 @@ ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 - ---changeset_00000000-0000-0000-0000-000000000002 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 1 - -POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 -Accept: application/json -Content-Type: application/json - -{} - ---changeset_00000000-0000-0000-0000-000000000002-- - ---batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt deleted file mode 100644 index 7c8d4ab875..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchRequestFunctionImportWithPostWithCustomHeader.txt +++ /dev/null @@ -1,18 +0,0 @@ ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 - ---changeset_00000000-0000-0000-0000-000000000002 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 1 - -POST awesomeFunction?FirstName='John'&LastName='Doe' HTTP/1.1 -Accept: application/json -Content-Type: application/json -foo: bar - -{} - ---changeset_00000000-0000-0000-0000-000000000002-- - ---batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt deleted file mode 100644 index cb7449970a..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchFunctionImportTest/BatchResponse.txt +++ /dev/null @@ -1,90 +0,0 @@ ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 201 Created -Location: https://localhost/service/TestEntities('new') -Content-Type: application/json - -{"d":{"IntegerValue":1337}} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"StringValue":"Foo","IntegerValue":9000}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: text/plain - -{"value":42} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 204 No response. -Content-Type: application/json - -{"value":["Something","here"]} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 400 Bad Request -Content-Type: application/json;charset=utf-8 -Content-Length: 1050 -dataserviceversion: 1.0 - -{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"awesomeFunction":"awesomeStuff"}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"awesomeFunction":["foo","bar"]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"IntegerValue":33,"StringValue":"Alice"}}} ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt deleted file mode 100644 index 7a25853f38..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequest.txt +++ /dev/null @@ -1,83 +0,0 @@ ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 1 - -GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 -Accept: application/json - - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 - ---changeset_00000000-0000-0000-0000-000000000002 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 2 - -POST Entities HTTP/1.1 -Accept: application/json -Content-Type: application/json - -{"IntegerValue":12} - ---changeset_00000000-0000-0000-0000-000000000002-- - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 3 - -GET Entities(9000) HTTP/1.1 -Accept: application/json - - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 - ---changeset_00000000-0000-0000-0000-000000000003 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 4 - -PATCH Entities(13) HTTP/1.1 -Accept: application/json -Content-Type: application/json - -{} - ---changeset_00000000-0000-0000-0000-000000000003 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 5 - -DELETE Entities(14) HTTP/1.1 -Accept: application/json - - ---changeset_00000000-0000-0000-0000-000000000003-- - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 - ---changeset_00000000-0000-0000-0000-000000000004 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 6 - -DELETE Entities(15) HTTP/1.1 -Accept: application/json - - ---changeset_00000000-0000-0000-0000-000000000004 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 7 - -DELETE Entities(16) HTTP/1.1 -Accept: application/json - - ---changeset_00000000-0000-0000-0000-000000000004-- - ---batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt deleted file mode 100644 index 4864bce1eb..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchRequestWithCustomHeaders.txt +++ /dev/null @@ -1,90 +0,0 @@ ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 1 - -GET Entities?$select=StringValue,IntegerValue&$top=10 HTTP/1.1 -Accept: application/json -header-read_all: read_all - - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000002 - ---changeset_00000000-0000-0000-0000-000000000002 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 2 - -POST Entities HTTP/1.1 -Accept: application/json -Content-Type: application/json -header-create: create - -{"IntegerValue":12} - ---changeset_00000000-0000-0000-0000-000000000002-- - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 3 - -GET Entities(9000) HTTP/1.1 -Accept: application/json -header-read_by_key: read_by_key - - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000003 - ---changeset_00000000-0000-0000-0000-000000000003 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 4 - -PATCH Entities(13) HTTP/1.1 -Accept: application/json -Content-Type: application/json -header-update: update - -{} - ---changeset_00000000-0000-0000-0000-000000000003 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 5 - -DELETE Entities(14) HTTP/1.1 -Accept: application/json -header-delete: delete-entity14 - - ---changeset_00000000-0000-0000-0000-000000000003-- - ---batch_00000000-0000-0000-0000-000000000001 -Content-Type: multipart/mixed;boundary=changeset_00000000-0000-0000-0000-000000000004 - ---changeset_00000000-0000-0000-0000-000000000004 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 6 - -DELETE Entities(15) HTTP/1.1 -Accept: application/json -header-delete: delete-entity15 - - ---changeset_00000000-0000-0000-0000-000000000004 -Content-Type: application/http -Content-Transfer-Encoding: binary -Content-ID: 7 - -DELETE Entities(16) HTTP/1.1 -Accept: application/json -header-delete: delete-entity16 - - ---changeset_00000000-0000-0000-0000-000000000004-- - ---batch_00000000-0000-0000-0000-000000000001-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt deleted file mode 100644 index 68718fe227..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODataV2BatchRequestUnitTest/BatchResponse.txt +++ /dev/null @@ -1,63 +0,0 @@ ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 201 Created -Location: https://localhost/service/TestEntities('new') -Content-Type: application/json - -{"d":{"IntegerValue":1337}} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"StringValue":"Foo","IntegerValue":9000}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: text/plain - -{"value":42} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 204 No response. -Content-Type: application/json - -{"value":["Something","here"]} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 400 Bad Request -Content-Type: application/json;charset=utf-8 -Content-Length: 1050 -dataserviceversion: 1.0 - -{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt deleted file mode 100644 index 040aa489ca..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithChangeset.txt +++ /dev/null @@ -1,62 +0,0 @@ ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 201 Created -Location: https://localhost/service/TestEntities('new') -Content-Type: application/json - -{"d":{"IntegerValue":1337}} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8cb4-- - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"StringValue":"Foo","IntegerValue":9000}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: multipart/mixed; boundary=changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 - ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: text/plain - -{"value":42} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 204 No response. -Content-Type: application/json - -{"value":["Something","here"]} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000 -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 201 Created -Location: https://localhost/service/TestEntities('new') -Content-Type: application/json - -{"d":{"IntegerValue":1447}} ---changesetresponse_e4c6cc48-c59e-42f8-bb00-1250fa2a8000-- ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt deleted file mode 100644 index 7da32711f4..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithError.txt +++ /dev/null @@ -1,29 +0,0 @@ ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"StringValue":"Foo","IntegerValue":9000}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 400 Bad Request -Content-Type: application/json;charset=utf-8 -Content-Length: 1050 -dataserviceversion: 1.0 - -{"error":{"code":"ZCU/100","message":{"lang":"nl","value":"Service 0000000003 0000000010 niet gevonden voor operatie 0410"},"innererror":{"application":{"component_id":"","service_namespace":"/SAP/","service_id":"ZCU_PE_ORDER_SRV","service_version":"0001"},"transactionid":"23F2932D54040110E005FD84A23B406E","timestamp":"20201215151501.0634490","Error_Resolution":{"SAP_Transaction":"Run transaction /IWFND/ERROR_LOG on SAP Gateway hub system (System Alias ) and search for entries with the timestamp above for more details","SAP_Note":"See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)","Batch_SAP_Note":"See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"},"errordetails":[{"code":"ZCU/100","message":"Service 0000000003 0000000010 niet gevonden voor operatie 0410","propertyref":"","severity":"error","target":""},{"code":"/IWBEP/CX_MGW_BUSI_EXCEPTION","message":"Fout bij wijzigen PE order.","propertyref":"","severity":"error","target":""}]}}} ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- diff --git a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt b/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt deleted file mode 100644 index cca2d42f9b..0000000000 --- a/datamodel/odata-core-apache-httpclient5/bin/src/test/resources/ODatav2BatchConnectionTest/BatchResponseWithoutChangeset.txt +++ /dev/null @@ -1,19 +0,0 @@ ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"results":[{"StringValue":"Foo","IntegerValue":42}]}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef -Content-Type: application/http -Content-Transfer-Encoding: binary - -HTTP/1.1 200 OK -Content-Type: application/json - -{"d":{"StringValue":"Foo","IntegerValue":9000}} - ---batchresponse_76ef6b0a-a0e2-4f31-9f70-f5d3f73a6bef-- From ebdb70ed83868e7c84cae6339c046ff554e53891 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Mon, 14 Sep 2026 11:58:25 -0300 Subject: [PATCH 8/9] remove stale TODO comments from HC5 OData modules to fix Checkstyle threshold --- .../datamodel/odata/helper/batch/BatchFluentHelperBasic.java | 1 - .../sdk/datamodel/odatav4/adapter/GsonCustomFieldAdapter.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java index 268b6f5aad..a14fd06829 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/batch/BatchFluentHelperBasic.java @@ -78,7 +78,6 @@ public abstract class BatchFluentHelperBasic @Override public void write( @Nonnull final JsonWriter out, @Nullable final Object value ) { - // TODO missing implementation for array. if( value instanceof String ) { gson.toJson(new JsonPrimitive((String) value), out); } @@ -51,7 +50,6 @@ public void write( @Nonnull final JsonWriter out, @Nullable final Object value ) public Object read( @Nonnull final JsonReader in ) throws IOException { - // TODO missing implementation for array. if( JsonToken.STRING == in.peek() ) { return in.nextString(); } From e66bff5c270e6bf74901305565b2dd9c95222f40 Mon Sep 17 00:00:00 2001 From: Ricardo Soares Date: Mon, 14 Sep 2026 15:09:44 -0300 Subject: [PATCH 9/9] suppress PMD CloseResource on VdmMediaEntity - stream ownership transferred to caller --- .../com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java index 479a87760c..80d4f6cd19 100644 --- a/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java +++ b/datamodel/odata-core-apache-httpclient5/src/main/java/com/sap/cloud/sdk/datamodel/odata/helper/VdmMediaEntity.java @@ -75,6 +75,7 @@ public InputStream fetchMediaStream() } final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); final ODataRequestResultGeneric result = request.execute(httpClient); + @SuppressWarnings( "PMD.CloseResource" ) // entity is intentionally not closed here because its content stream is returned to the caller final HttpEntity entity = result.getHttpResponse().getEntity(); if( entity == null ) { throw new ODataResponseException(