From 67eb43702f86c40d3d653756052a272bd62911ae Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 2 Jun 2022 20:41:17 +0200 Subject: [PATCH 1/5] initial draft --- common/util/build.gradle.kts | 1 + .../common/reflection/ReflectionUtil.java | 19 ++++++---- .../InMemoryContractNegotiationStore.java | 12 +++++- .../InMemoryContractNegotiationStoreTest.java | 37 +++++++++++++++++++ .../asset/service/AssetServiceImpl.java | 28 +++++++------- .../store/CosmosContractNegotiationStore.java | 9 +++++ ...ntractNegotiationStoreIntegrationTest.java | 35 ++++++++++++++++++ .../store/SqlContractNegotiationStore.java | 19 ++++++++++ .../SqlContractNegotiationStoreTest.java | 37 +++++++++++++++++++ .../store/ContractNegotiationStore.java | 19 +++++++--- 10 files changed, 187 insertions(+), 29 deletions(-) diff --git a/common/util/build.gradle.kts b/common/util/build.gradle.kts index 687b878b2c5..992f0ff9380 100644 --- a/common/util/build.gradle.kts +++ b/common/util/build.gradle.kts @@ -35,6 +35,7 @@ publishing { create("common-util") { artifactId = "common-util" from(components["java"]) + artifacts.forEach { a -> println(a.file) } } } } diff --git a/common/util/src/main/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtil.java b/common/util/src/main/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtil.java index f03369f4e74..520e8f531a4 100644 --- a/common/util/src/main/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtil.java +++ b/common/util/src/main/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtil.java @@ -33,13 +33,14 @@ public class ReflectionUtil { private static final String CLOSING_BRACKET = "]"; /** - * Utility function to get value of a field from an object. For field names currently the dot notation and array indexers are supported: + * Utility function to get value of a field from an object. For field names currently the dot notation and array + * indexers are supported: *
      *     someObject.someValue
      *     someObject[2].someValue //someObject must impement the List interface
      * 
* - * @param object The object + * @param object The object * @param propertyName The name of the field * @return The field's value. * @throws ReflectionException if the field does not exist or is not accessible @@ -53,6 +54,9 @@ public static T getFieldValue(String propertyName, Object object) { var field = propertyName.substring(0, dotIx); var rest = propertyName.substring(dotIx + 1); object = getFieldValue(field, object); + if (object == null) { + return null; + } return getFieldValue(rest, object); } else if (propertyName.matches(ARRAY_INDEXER_REGEX)) { //array indexer var openingBracketIx = propertyName.indexOf(OPENING_BRACKET); @@ -82,10 +86,10 @@ public static T getFieldValue(String propertyName, Object object) { /** - * Utility function to get value of a field from an object. Essentially the same as {@link ReflectionUtil#getFieldValue(String, Object)} - * but it does not throw an exception + * Utility function to get value of a field from an object. Essentially the same as + * {@link ReflectionUtil#getFieldValue(String, Object)} but it does not throw an exception * - * @param object The object + * @param object The object * @param propertyName The name of the field * @return The field's value. Returns null if the field does not exist or is inaccessible. */ @@ -118,9 +122,10 @@ public static Comparator propertyComparator(boolean isAscending, String p /** - * Gets a field with a given name from all declared fields of a class including supertypes. Will include protected and private fields. + * Gets a field with a given name from all declared fields of a class including supertypes. Will include protected + * and private fields. * - * @param clazz The class of the object + * @param clazz The class of the object * @param fieldName The fieldname * @return A field with the given name, null if the field does not exist */ diff --git a/core/defaults/src/main/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStore.java b/core/defaults/src/main/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStore.java index 2ac3f2bec1e..5ccc69143e5 100644 --- a/core/defaults/src/main/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStore.java +++ b/core/defaults/src/main/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStore.java @@ -36,11 +36,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.lang.String.format; import static java.util.stream.Collectors.toList; /** - * An in-memory, threadsafe process store. - * This implementation is intended for testing purposes only. + * An in-memory, threadsafe process store. This implementation is intended for testing purposes only. */ public class InMemoryContractNegotiationStore implements ContractNegotiationStore { @@ -124,6 +124,14 @@ public void delete(String processId) { return lockManager.readLock(() -> agreementQueryResolver.query(getAgreements(), querySpec)); } + @Override + public Stream getNegotiationsWithAgreementOnAsset(String assetId) { + var filter = format("contractAgreement.assetId = %s", assetId); + var query = QuerySpec.Builder.newInstance().filter(filter).build(); + + return queryNegotiations(query); + } + @Override public @NotNull List nextForState(int state, int max) { return lockManager.readLock(() -> { diff --git a/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java b/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java index 1e486427589..1c7c9f042c1 100644 --- a/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java +++ b/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java @@ -20,6 +20,7 @@ import org.eclipse.dataspaceconnector.spi.query.SortOrder; import org.eclipse.dataspaceconnector.spi.types.domain.contract.agreement.ContractAgreement; import org.eclipse.dataspaceconnector.spi.types.domain.contract.negotiation.ContractNegotiation; +import org.eclipse.dataspaceconnector.spi.types.domain.contract.negotiation.ContractNegotiationStates; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -28,6 +29,7 @@ import java.util.Comparator; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; @@ -320,6 +322,41 @@ void queryAgreements_verifySorting_invalidProperty() { assertThat(store.queryAgreements(query)).isEmpty(); } + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithAgreement() { + var agreement = createAgreementBuilder().id("contract1").build(); + var negotiation = createNegotiationBuilder("negotiation1").contractAgreement(agreement).build(); + var assetId = agreement.getAssetId(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).hasSize(1).usingRecursiveFieldByFieldElementComparator().containsOnly(negotiation); + } + + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() { + var assetId = UUID.randomUUID().toString(); + var negotiation = ContractNegotiation.Builder.newInstance() + .type(ContractNegotiation.Type.CONSUMER) + .id("negotiation1") + .contractAgreement(null) + .correlationId("corr-negotiation1") + .state(ContractNegotiationStates.REQUESTED.code()) + .counterPartyAddress("consumer") + .counterPartyId("consumerId") + .protocol("ids-multipart") + .build(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).isEmpty(); + assertThat(store.queryAgreements(QuerySpec.none())).isEmpty(); + } + @NotNull private ContractNegotiation requestingNegotiation() { var negotiation = createNegotiation(UUID.randomUUID().toString()); diff --git a/extensions/api/data-management/asset/src/main/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImpl.java b/extensions/api/data-management/asset/src/main/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImpl.java index 1f46664df6d..47096b17ec2 100644 --- a/extensions/api/data-management/asset/src/main/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImpl.java +++ b/extensions/api/data-management/asset/src/main/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImpl.java @@ -51,13 +51,23 @@ public Collection query(QuerySpec query) { return transactionContext.execute(() -> index.queryAssets(query).collect(toList())); } + @Override + public ServiceResult create(Asset asset, DataAddress dataAddress) { + return transactionContext.execute(() -> { + if (findById(asset.getId()) == null) { + loader.accept(asset, dataAddress); + return ServiceResult.success(asset); + } else { + return ServiceResult.conflict(format("Asset %s cannot be created because it already exist", asset.getId())); + } + }); + } + @Override public ServiceResult delete(String assetId) { return transactionContext.execute(() -> { - var filter = format("contractAgreement.assetId = %s", assetId); - var query = QuerySpec.Builder.newInstance().filter(filter).build(); - var negotiationsOnAsset = contractNegotiationStore.queryNegotiations(query); + var negotiationsOnAsset = contractNegotiationStore.getNegotiationsWithAgreementOnAsset(assetId); if (negotiationsOnAsset.findAny().isPresent()) { return ServiceResult.conflict(format("Asset %s cannot be deleted as it is referenced by at least one contract agreement", assetId)); } @@ -70,16 +80,4 @@ public ServiceResult delete(String assetId) { return ServiceResult.success(deleted); }); } - - @Override - public ServiceResult create(Asset asset, DataAddress dataAddress) { - return transactionContext.execute(() -> { - if (findById(asset.getId()) == null) { - loader.accept(asset, dataAddress); - return ServiceResult.success(asset); - } else { - return ServiceResult.conflict(format("Asset %s cannot be created because it already exist", asset.getId())); - } - }); - } } diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/main/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStore.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/main/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStore.java index 0460e0a4499..d2c3fee8997 100644 --- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/main/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStore.java +++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/main/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStore.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.lang.String.format; import static java.util.Optional.ofNullable; import static net.jodah.failsafe.Failsafe.with; @@ -162,6 +163,14 @@ public void delete(String negotiationId) { .filter(Objects::nonNull); } + @Override + public Stream getNegotiationsWithAgreementOnAsset(String assetId) { + var filter = format("contractAgreement.assetId = %s", assetId); + var query = QuerySpec.Builder.newInstance().filter(filter).build(); + + return queryNegotiations(query); + } + @Override public @NotNull List nextForState(int state, int max) { diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java index bffbd58b99f..e1619664874 100644 --- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java +++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java @@ -544,6 +544,41 @@ void queryAgreements_verifySorting_invalidProperty() { assertThat(agreements).isEmpty(); } + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithAgreement() { + var agreement = generateAgreementBuilder().id("contract1").build(); + var negotiation = generateNegotiationBuilder("negotiation1").contractAgreement(agreement).build(); + var assetId = agreement.getAssetId(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).hasSize(1).usingRecursiveFieldByFieldElementComparator().containsOnly(negotiation); + } + + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() { + var assetId = UUID.randomUUID().toString(); + var negotiation = ContractNegotiation.Builder.newInstance() + .type(ContractNegotiation.Type.CONSUMER) + .id("negotiation1") + .contractAgreement(null) + .correlationId("corr-negotiation1") + .state(ContractNegotiationStates.REQUESTED.code()) + .counterPartyAddress("consumer") + .counterPartyId("consumerId") + .protocol("ids-multipart") + .build(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).isEmpty(); + assertThat(store.queryAgreements(QuerySpec.none())).isEmpty(); + } + private ContractNegotiationDocument toDocument(Object object) { var json = typeManager.writeValueAsString(object); return typeManager.readValue(json, ContractNegotiationDocument.class); diff --git a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java index 1dd73c4e2f6..2df93b4239e 100644 --- a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java +++ b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java @@ -191,6 +191,25 @@ public void delete(String negotiationId) { }); } + @Override + public Stream getNegotiationsWithAgreementOnAsset(String assetId) { + var statement = "\n" + + "SELECT *\n" + + "FROM edc_contract_negotiation\n" + + "INNER JOIN edc_contract_agreement eca on edc_contract_negotiation.contract_agreement_id = eca.agreement_id\n" + + "WHERE edc_contract_negotiation.contract_agreement_id in (SELECT agreement_id\n" + + " FROM edc_contract_agreement\n" + + " WHERE asset_id = ?);\n"; + + return transactionContext.execute(() -> { + try (var connection = getConnection()) { + return executeQuery(connection, this::mapContractNegotiation, statement, assetId).stream(); + } catch (SQLException e) { + throw new EdcPersistenceException(e); + } + }); + } + @Override public @NotNull List nextForState(int state, int max) { return transactionContext.execute(() -> { diff --git a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java index 2f2533a21e2..ea9fd3b368f 100644 --- a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java +++ b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java @@ -401,6 +401,43 @@ void queryNegotiations() { .allMatch(i -> i > 4 && i < 15); } + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithAgreement() { + var agreement = createContract("contract1"); + var negotiation = createNegotiation("negotiation1", agreement); + var assetId = agreement.getAssetId(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).hasSize(1).usingRecursiveFieldByFieldElementComparator().containsOnly(negotiation); + + } + + @Test + void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() { + var assetId = UUID.randomUUID().toString(); + var negotiation = ContractNegotiation.Builder.newInstance() + .type(ContractNegotiation.Type.CONSUMER) + .id("negotiation1") + .contractAgreement(null) + .correlationId("corr-negotiation1") + .state(ContractNegotiationStates.REQUESTED.code()) + .counterPartyAddress("consumer") + .counterPartyId("consumerId") + .protocol("ids-multipart") + .build(); + + store.save(negotiation); + + var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList()); + + assertThat(result).isEmpty(); + assertThat(store.queryAgreements(QuerySpec.none())).isEmpty(); + + } + @Test @DisplayName("Verify that paging is used") void queryNegotiations_withAgreement() { diff --git a/spi/contract-spi/src/main/java/org/eclipse/dataspaceconnector/spi/contract/negotiation/store/ContractNegotiationStore.java b/spi/contract-spi/src/main/java/org/eclipse/dataspaceconnector/spi/contract/negotiation/store/ContractNegotiationStore.java index 9a2c450d1ba..b5c53d737e9 100644 --- a/spi/contract-spi/src/main/java/org/eclipse/dataspaceconnector/spi/contract/negotiation/store/ContractNegotiationStore.java +++ b/spi/contract-spi/src/main/java/org/eclipse/dataspaceconnector/spi/contract/negotiation/store/ContractNegotiationStore.java @@ -49,7 +49,8 @@ public interface ContractNegotiationStore extends StateEntityStore * The general order of precedence of the query parameters is: *
@@ -85,12 +86,20 @@ public interface ContractNegotiationStore extends StateEntityStore queryAgreements(QuerySpec querySpec);
+
+    /**
+     * Finds all negotiations, that have agreements targeting the given asset
+     *
+     * @param assetId The asset for which the negotiations + agreements are wanted.
+     * @return A stream of contract negotiations, or empty
+     */
+    Stream getNegotiationsWithAgreementOnAsset(String assetId);
 }

From cbea5fe72dd2415b4f033449fa44007a40c691b7 Mon Sep 17 00:00:00 2001
From: Paul Latzelsperger 
Date: Fri, 3 Jun 2022 08:29:11 +0200
Subject: [PATCH 2/5] fixed test

---
 .../asset/service/AssetServiceImplTest.java   | 22 +++++++++++--------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java b/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
index 95357106500..ce1f6e2d8bc 100644
--- a/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
+++ b/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 class AssetServiceImplTest {
@@ -73,15 +74,17 @@ void query_shouldRelyOnAssetIndex() {
 
     @Test
     void createAsset_shouldCreateAssetIfItDoesNotAlreadyExist() {
-        var asset = createAsset("assetId");
-        when(index.findById("assetId")).thenReturn(null);
-        var dataAddress = DataAddress.Builder.newInstance().type("addressType").build();
+        var assetId = "assetId";
+        var asset = createAsset(assetId);
+        when(index.findById(assetId)).thenReturn(null);
+        var addressType = "addressType";
+        var dataAddress = DataAddress.Builder.newInstance().type(addressType).build();
 
         var inserted = service.create(asset, dataAddress);
 
         assertThat(inserted.succeeded()).isTrue();
-        assertThat(inserted.getContent()).matches(hasId("assetId"));
-        verify(loader).accept(argThat(it -> "assetId".equals(it.getId())), argThat(it -> "addressType".equals(it.getType())));
+        assertThat(inserted.getContent()).matches(hasId(assetId));
+        verify(loader).accept(argThat(it -> assetId.equals(it.getId())), argThat(it -> addressType.equals(it.getType())));
     }
 
     @Test
@@ -120,16 +123,17 @@ void delete_shouldNotDeleteIfAssetIsAlreadyPartOfAnAgreement() {
                         .id(UUID.randomUUID().toString())
                         .providerAgentId(UUID.randomUUID().toString())
                         .consumerAgentId(UUID.randomUUID().toString())
-                        .assetId("assetId")
-                        .policy(Policy.Builder.newInstance().build())
-                        .build())
+                        .assetId(asset.getId())
+                        .policy(Policy.Builder.newInstance().build())                        .build())
                 .build();
-        when(contractNegotiationStore.queryNegotiations(any())).thenReturn(Stream.of(contractNegotiation));
+        when(contractNegotiationStore.getNegotiationsWithAgreementOnAsset(any())).thenReturn(Stream.of(contractNegotiation));
 
         var deleted = service.delete("assetId");
 
         assertThat(deleted.failed()).isTrue();
         assertThat(deleted.getFailure().getReason()).isEqualTo(CONFLICT);
+        verify(contractNegotiationStore).getNegotiationsWithAgreementOnAsset(any());
+        verifyNoMoreInteractions(contractNegotiationStore);
     }
 
     @Test

From fd6c73cf91289c815ef27ef14395d9b2e131b442 Mon Sep 17 00:00:00 2001
From: Paul Latzelsperger 
Date: Fri, 3 Jun 2022 09:31:34 +0200
Subject: [PATCH 3/5] added more tests

---
 .../common/reflection/AnotherObject.java      | 23 ++++++
 .../common/reflection/ReflectionUtilTest.java | 18 ++++
 .../reflection/TestObjectSubSubclass.java     |  9 ++
 .../InMemoryContractNegotiationStoreTest.java | 16 ++++
 ...ntractNegotiationStoreIntegrationTest.java | 82 +++++++++++--------
 .../CosmosContractNegotiationStoreTest.java   |  3 +-
 .../negotiation/store/TestFunctions.java      | 30 ++++---
 ...tNegotiationDocumentSerializationTest.java |  6 +-
 .../contractnegotiation/TestFunctions.java    | 48 +++++++----
 .../SqlContractNegotiationStoreTest.java      | 28 ++++---
 10 files changed, 187 insertions(+), 76 deletions(-)
 create mode 100644 common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/AnotherObject.java

diff --git a/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/AnotherObject.java b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/AnotherObject.java
new file mode 100644
index 00000000000..b85cbba7e85
--- /dev/null
+++ b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/AnotherObject.java
@@ -0,0 +1,23 @@
+/*
+ *  Copyright (c) 2020 - 2022 Microsoft Corporation
+ *
+ *  This program and the accompanying materials are made available under the
+ *  terms of the Apache License, Version 2.0 which is available at
+ *  https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Contributors:
+ *       Microsoft Corporation - initial API and implementation
+ *
+ */
+
+package org.eclipse.dataspaceconnector.common.reflection;
+
+public class AnotherObject {
+    private final String anotherDescription;
+
+    public AnotherObject(String desc) {
+        anotherDescription = desc;
+    }
+}
diff --git a/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtilTest.java b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtilTest.java
index 3ba32d5fd3f..106b811bca7 100644
--- a/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtilTest.java
+++ b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/ReflectionUtilTest.java
@@ -158,6 +158,24 @@ void getFieldRecursive_whenNotDeclared() {
         assertThat(ReflectionUtil.getFieldRecursive(to.getClass(), "notExist")).isNull();
     }
 
+    @Test
+    void getFieldValue_whenParentExist() {
+        var to = new TestObjectSubSubclass("test-desc", 1, "foobar");
+        to.setAnotherObject(new AnotherObject("another-desc"));
+
+        String fieldValue = ReflectionUtil.getFieldValue("anotherObject.anotherDescription", to);
+        assertThat(fieldValue).isEqualTo("another-desc");
+    }
+
+    @Test
+    void getFieldValue_whenParentNotExist() {
+        var to = new TestObjectSubSubclass("test-desc", 1, "foobar");
+        to.setAnotherObject(null);
+
+        String fieldValue = ReflectionUtil.getFieldValue("anotherObject.anotherDescription", to);
+        assertThat(fieldValue).isNull();
+    }
+
     @Test
     void getFieldValue_withArrayIndex() {
         var to1 = new TestObject("to1", 420);
diff --git a/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/TestObjectSubSubclass.java b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/TestObjectSubSubclass.java
index 5e04fd3ebb9..e5477bcdcf6 100644
--- a/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/TestObjectSubSubclass.java
+++ b/common/util/src/test/java/org/eclipse/dataspaceconnector/common/reflection/TestObjectSubSubclass.java
@@ -16,6 +16,7 @@
 
 public class TestObjectSubSubclass extends TestObjectSubclass {
     private final String description;
+    private AnotherObject anotherObject;
 
     public TestObjectSubSubclass(String description, int priority, String testProperty) {
         super(description, priority, testProperty);
@@ -26,4 +27,12 @@ public TestObjectSubSubclass(String description, int priority, String testProper
     public String getDescription() {
         return description;
     }
+
+    public AnotherObject getAnotherObject() {
+        return anotherObject;
+    }
+
+    public void setAnotherObject(AnotherObject anotherObject) {
+        this.anotherObject = anotherObject;
+    }
 }
diff --git a/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java b/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java
index 1c7c9f042c1..c9355046188 100644
--- a/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java
+++ b/core/defaults/src/test/java/org/eclipse/dataspaceconnector/core/defaults/negotiationstore/InMemoryContractNegotiationStoreTest.java
@@ -357,6 +357,22 @@ void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() {
         assertThat(store.queryAgreements(QuerySpec.none())).isEmpty();
     }
 
+    @Test
+    void getNegotiationsWithAgreementOnAsset_multipleNegotiationsSameAsset() {
+        var assetId = UUID.randomUUID().toString();
+        var negotiation1 = createNegotiationBuilder("negotiation1").contractAgreement(createAgreementBuilder().id("contract1").assetId(assetId).build()).build();
+        var negotiation2 = createNegotiationBuilder("negotiation2").contractAgreement(createAgreementBuilder().id("contract2").assetId(assetId).build()).build();
+
+        store.save(negotiation1);
+        store.save(negotiation2);
+
+        var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList());
+
+        assertThat(result).hasSize(2)
+                .extracting(ContractNegotiation::getId).containsExactlyInAnyOrder("negotiation1", "negotiation2");
+
+    }
+
     @NotNull
     private ContractNegotiation requestingNegotiation() {
         var negotiation = createNegotiation(UUID.randomUUID().toString());
diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java
index e1619664874..aaac43e3474 100644
--- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java
+++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreIntegrationTest.java
@@ -56,10 +56,10 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.awaitility.Awaitility.await;
-import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateAgreementBuilder;
+import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.createContractBuilder;
+import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.createNegotiation;
+import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.createNegotiationBuilder;
 import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateDocument;
-import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateNegotiation;
-import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateNegotiationBuilder;
 
 @AzureCosmosDbIntegrationTest
 class CosmosContractNegotiationStoreIntegrationTest {
@@ -178,7 +178,7 @@ void findContractAgreement_notFound() {
 
     @Test
     void save_notExists_shouldCreate() {
-        var negotiation = generateNegotiation();
+        var negotiation = TestFunctions.createNegotiation();
         store.save(negotiation);
 
         var allObjs = container.readAllItems(new PartitionKey(partitionKey), Object.class);
@@ -188,7 +188,7 @@ void save_notExists_shouldCreate() {
 
     @Test
     void save_exists_shouldUpdate() {
-        var negotiation = generateNegotiation();
+        var negotiation = TestFunctions.createNegotiation();
         container.createItem(new ContractNegotiationDocument(negotiation, partitionKey));
 
         assertThat(container.readAllItems(new PartitionKey(partitionKey), Object.class)).hasSize(1);
@@ -208,7 +208,7 @@ void save_exists_shouldUpdate() {
 
     @Test
     void save_leasedByOther_shouldRaiseException() {
-        var negotiation = generateNegotiation("test-id", ContractNegotiationStates.CONFIRMED);
+        var negotiation = createNegotiation("test-id", ContractNegotiationStates.CONFIRMED);
         var item = new ContractNegotiationDocument(negotiation, partitionKey);
         item.acquireLease("someone-else");
         container.createItem(item);
@@ -220,7 +220,7 @@ void save_leasedByOther_shouldRaiseException() {
 
     @Test
     void delete_leasedByOther_shouldRaiseException() {
-        var negotiation = generateNegotiation("test-id", ContractNegotiationStates.CONFIRMED);
+        var negotiation = createNegotiation("test-id", ContractNegotiationStates.CONFIRMED);
         var item = new ContractNegotiationDocument(negotiation, partitionKey);
         item.acquireLease("someone-else");
         container.createItem(item);
@@ -231,7 +231,7 @@ void delete_leasedByOther_shouldRaiseException() {
     @Test
     void nextForState() {
         var state = ContractNegotiationStates.CONFIRMED;
-        var n = generateNegotiation(state);
+        var n = TestFunctions.createNegotiation(state);
         container.createItem(new ContractNegotiationDocument(n, partitionKey));
 
         var result = store.nextForState(state.code(), 10);
@@ -244,7 +244,7 @@ void nextForState_exceedsLimit() {
         var numElements = 10;
 
         var preparedNegotiations = IntStream.range(0, numElements)
-                .mapToObj(i -> generateNegotiation(state))
+                .mapToObj(i -> TestFunctions.createNegotiation(state))
                 .peek(n -> container.createItem(new ContractNegotiationDocument(n, partitionKey)))
                 .collect(Collectors.toList());
 
@@ -255,7 +255,7 @@ void nextForState_exceedsLimit() {
     @Test
     void nextForState_noResult() {
         var state = ContractNegotiationStates.CONFIRMED;
-        var n = generateNegotiation(state);
+        var n = TestFunctions.createNegotiation(state);
         container.createItem(new ContractNegotiationDocument(n, partitionKey));
 
         var result = store.nextForState(ContractNegotiationStates.PROVIDER_OFFERING.code(), 10);
@@ -265,15 +265,15 @@ void nextForState_noResult() {
     @Test
     void nextForState_onlyReturnsFreeItems() {
         var state = ContractNegotiationStates.CONFIRMED;
-        var n1 = generateNegotiation(state);
+        var n1 = TestFunctions.createNegotiation(state);
         var doc1 = new ContractNegotiationDocument(n1, partitionKey);
         container.createItem(doc1);
 
-        var n2 = generateNegotiation(state);
+        var n2 = TestFunctions.createNegotiation(state);
         var doc2 = new ContractNegotiationDocument(n2, partitionKey);
         container.createItem(doc2);
 
-        var n3 = generateNegotiation(state);
+        var n3 = TestFunctions.createNegotiation(state);
         var doc3 = new ContractNegotiationDocument(n3, partitionKey);
         doc3.acquireLease("another-connector");
         container.createItem(doc3);
@@ -285,7 +285,7 @@ void nextForState_onlyReturnsFreeItems() {
     @Test
     void nextForState_leasedBySelf() {
         var state = ContractNegotiationStates.CONFIRMED;
-        var n = generateNegotiation(state);
+        var n = TestFunctions.createNegotiation(state);
         var doc = new ContractNegotiationDocument(n, partitionKey);
         container.createItem(doc);
 
@@ -303,7 +303,7 @@ void nextForState_leasedBySelf() {
     @Test
     void nextForState_leasedByAnotherExpired() {
         var state = ContractNegotiationStates.CONFIRMED;
-        var n = generateNegotiation(state);
+        var n = TestFunctions.createNegotiation(state);
         var doc = new ContractNegotiationDocument(n, partitionKey);
         Duration leaseDuration = Duration.ofSeconds(10); // give it some time to compensate for TOF delays
         doc.acquireLease("another-connector", leaseDuration);
@@ -325,7 +325,7 @@ void nextForState_leasedByAnotherExpired() {
 
     @Test
     void nextForState_verifySaveClearsLease() {
-        var n = generateNegotiation("test-id", ContractNegotiationStates.CONSUMER_OFFERED);
+        var n = createNegotiation("test-id", ContractNegotiationStates.CONSUMER_OFFERED);
         var doc = new ContractNegotiationDocument(n, partitionKey);
         container.createItem(doc);
 
@@ -351,7 +351,7 @@ void nextForState_verifySaveClearsLease() {
     @Test
     @DisplayName("Verify that a leased entity can still be deleted")
     void nextForState_verifyDelete() {
-        var n = generateNegotiation("test-id", ContractNegotiationStates.CONSUMER_OFFERED);
+        var n = createNegotiation("test-id", ContractNegotiationStates.CONSUMER_OFFERED);
         var doc = new ContractNegotiationDocument(n, partitionKey);
         container.createItem(doc);
 
@@ -454,8 +454,8 @@ void findAll_verifySortingInvalidSortField() {
 
     @Test
     void getAgreementsForDefinitionId() {
-        var contractAgreement = generateAgreementBuilder().id(ContractId.createContractId("definitionId")).build();
-        var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+        var contractAgreement = createContractBuilder().id(ContractId.createContractId("definitionId")).build();
+        var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
         store.save(negotiation);
 
         var result = store.getAgreementsForDefinitionId("definitionId");
@@ -465,8 +465,8 @@ void getAgreementsForDefinitionId() {
 
     @Test
     void getAgreementsForDefinitionId_notFound() {
-        var contractAgreement = generateAgreementBuilder().id(ContractId.createContractId("otherDefinitionId")).build();
-        var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+        var contractAgreement = createContractBuilder().id(ContractId.createContractId("otherDefinitionId")).build();
+        var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
         store.save(negotiation);
 
         var result = store.getAgreementsForDefinitionId("definitionId");
@@ -477,8 +477,8 @@ void getAgreementsForDefinitionId_notFound() {
     @Test
     void queryAgreements_noQuerySpec() {
         IntStream.range(0, 10).forEach(i -> {
-            var contractAgreement = generateAgreementBuilder().id(ContractId.createContractId(UUID.randomUUID().toString())).build();
-            var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+            var contractAgreement = createContractBuilder().id(ContractId.createContractId(UUID.randomUUID().toString())).build();
+            var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
             store.save(negotiation);
         });
 
@@ -490,8 +490,8 @@ void queryAgreements_noQuerySpec() {
     @Test
     void queryAgreements_verifyPaging() {
         IntStream.range(0, 10).forEach(i -> {
-            var contractAgreement = generateAgreementBuilder().id(ContractId.createContractId(UUID.randomUUID().toString())).build();
-            var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+            var contractAgreement = createContractBuilder().id(ContractId.createContractId(UUID.randomUUID().toString())).build();
+            var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
             store.save(negotiation);
         });
 
@@ -505,8 +505,8 @@ void queryAgreements_verifyPaging() {
     @Test
     void queryAgreements_verifyFiltering() {
         IntStream.range(0, 10).forEach(i -> {
-            var contractAgreement = generateAgreementBuilder().id(i + ":" + i).build();
-            var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+            var contractAgreement = createContractBuilder().id(i + ":" + i).build();
+            var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
             store.save(negotiation);
         });
         var query = QuerySpec.Builder.newInstance().equalsAsContains(false).filter("id=3:3").build();
@@ -519,8 +519,8 @@ void queryAgreements_verifyFiltering() {
     @Test
     void queryAgreements_verifySorting() {
         IntStream.range(0, 9).forEach(i -> {
-            var contractAgreement = generateAgreementBuilder().id(i + ":" + i).build();
-            var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+            var contractAgreement = createContractBuilder().id(i + ":" + i).build();
+            var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
             store.save(negotiation);
         });
 
@@ -533,8 +533,8 @@ void queryAgreements_verifySorting() {
     @Test
     void queryAgreements_verifySorting_invalidProperty() {
         IntStream.range(0, 10).forEach(i -> {
-            var contractAgreement = generateAgreementBuilder().id(i + ":" + i).build();
-            var negotiation = generateNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
+            var contractAgreement = createContractBuilder().id(i + ":" + i).build();
+            var negotiation = createNegotiationBuilder(UUID.randomUUID().toString()).contractAgreement(contractAgreement).build();
             store.save(negotiation);
         });
 
@@ -546,8 +546,8 @@ void queryAgreements_verifySorting_invalidProperty() {
 
     @Test
     void getNegotiationsWithAgreementOnAsset_negotiationWithAgreement() {
-        var agreement = generateAgreementBuilder().id("contract1").build();
-        var negotiation = generateNegotiationBuilder("negotiation1").contractAgreement(agreement).build();
+        var agreement = createContractBuilder().id("contract1").build();
+        var negotiation = createNegotiationBuilder("negotiation1").contractAgreement(agreement).build();
         var assetId = agreement.getAssetId();
 
         store.save(negotiation);
@@ -579,6 +579,22 @@ void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() {
         assertThat(store.queryAgreements(QuerySpec.none())).isEmpty();
     }
 
+    @Test
+    void getNegotiationsWithAgreementOnAsset_multipleNegotiationsSameAsset() {
+        var assetId = UUID.randomUUID().toString();
+        var negotiation1 = createNegotiation("negotiation1", createContractBuilder("contract1").assetId(assetId).build());
+        var negotiation2 = createNegotiation("negotiation2", createContractBuilder("contract2").assetId(assetId).build());
+
+        store.save(negotiation1);
+        store.save(negotiation2);
+
+        var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList());
+
+        assertThat(result).hasSize(2)
+                .extracting(ContractNegotiation::getId).containsExactlyInAnyOrder("negotiation1", "negotiation2");
+
+    }
+
     private ContractNegotiationDocument toDocument(Object object) {
         var json = typeManager.writeValueAsString(object);
         return typeManager.readValue(json, ContractNegotiationDocument.class);
diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreTest.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreTest.java
index 129832ce802..f58787f2779 100644
--- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreTest.java
+++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/CosmosContractNegotiationStoreTest.java
@@ -36,7 +36,6 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateDocument;
-import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateNegotiation;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -103,7 +102,7 @@ void findContractAgreement() {
 
     @Test
     void save() {
-        var negotiation = generateNegotiation();
+        var negotiation = TestFunctions.createNegotiation();
 
         store.save(negotiation);
 
diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/TestFunctions.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/TestFunctions.java
index 741fe675145..675d422495f 100644
--- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/TestFunctions.java
+++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/TestFunctions.java
@@ -28,22 +28,26 @@
 
 public class TestFunctions {
 
-    public static ContractNegotiation generateNegotiation() {
-        return generateNegotiation(ContractNegotiationStates.UNSAVED);
+    public static ContractNegotiation createNegotiation() {
+        return createNegotiation(ContractNegotiationStates.UNSAVED);
     }
 
-    public static ContractNegotiation generateNegotiation(ContractNegotiationStates state) {
-        return generateNegotiation(UUID.randomUUID().toString(), state);
+    public static ContractNegotiation createNegotiation(ContractNegotiationStates state) {
+        return createNegotiation(UUID.randomUUID().toString(), state);
     }
 
-    public static ContractNegotiation generateNegotiation(String id, ContractNegotiationStates state) {
-        return generateNegotiationBuilder(id)
+    public static ContractNegotiation createNegotiation(String id, ContractNegotiationStates state) {
+        return createNegotiationBuilder(id)
                 .state(state.code())
-                .contractAgreement(generateAgreementBuilder().build())
+                .contractAgreement(createContractBuilder().build())
                 .build();
     }
 
-    public static ContractNegotiation.Builder generateNegotiationBuilder(String id) {
+    public static ContractNegotiation createNegotiation(String id, ContractAgreement agreement) {
+        return createNegotiationBuilder(id).contractAgreement(agreement).build();
+    }
+
+    public static ContractNegotiation.Builder createNegotiationBuilder(String id) {
         return ContractNegotiation.Builder.newInstance()
                 .id(id)
                 .correlationId(UUID.randomUUID().toString())
@@ -53,7 +57,11 @@ public static ContractNegotiation.Builder generateNegotiationBuilder(String id)
                 .stateCount(1);
     }
 
-    public static ContractAgreement.Builder generateAgreementBuilder() {
+    public static ContractAgreement.Builder createContractBuilder() {
+        return createContractBuilder("1:2");
+    }
+
+    public static ContractAgreement.Builder createContractBuilder(String id) {
         return ContractAgreement.Builder.newInstance()
                 .providerAgentId("provider")
                 .consumerAgentId("consumer")
@@ -62,11 +70,11 @@ public static ContractAgreement.Builder generateAgreementBuilder() {
                 .contractStartDate(Instant.now().getEpochSecond())
                 .contractEndDate(Instant.now().plus(1, ChronoUnit.DAYS).getEpochSecond())
                 .contractSigningDate(Instant.now().getEpochSecond())
-                .id("1:2");
+                .id(id);
     }
 
     public static ContractNegotiationDocument generateDocument() {
-        return generateDocument(generateNegotiation());
+        return generateDocument(createNegotiation());
     }
 
     public static ContractNegotiationDocument generateDocument(ContractNegotiation negotiation) {
diff --git a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/model/ContractNegotiationDocumentSerializationTest.java b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/model/ContractNegotiationDocumentSerializationTest.java
index 127b9dc527a..b9a7afa44ee 100644
--- a/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/model/ContractNegotiationDocumentSerializationTest.java
+++ b/extensions/azure/cosmos/contract-negotiation-store-cosmos/src/test/java/org/eclipse/dataspaceconnector/contract/negotiation/store/model/ContractNegotiationDocumentSerializationTest.java
@@ -14,12 +14,12 @@
 
 package org.eclipse.dataspaceconnector.contract.negotiation.store.model;
 
+import org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions;
 import org.eclipse.dataspaceconnector.spi.types.TypeManager;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.eclipse.dataspaceconnector.contract.negotiation.store.TestFunctions.generateNegotiation;
 
 class ContractNegotiationDocumentSerializationTest {
 
@@ -34,7 +34,7 @@ void setup() {
 
     @Test
     void testSerialization() {
-        var def = generateNegotiation();
+        var def = TestFunctions.createNegotiation();
         var pk = def.getState();
 
         var document = new ContractNegotiationDocument(def, partitionKey);
@@ -49,7 +49,7 @@ void testSerialization() {
 
     @Test
     void testDeserialization() {
-        var def = generateNegotiation();
+        var def = TestFunctions.createNegotiation();
 
         var document = new ContractNegotiationDocument(def, partitionKey);
         String json = typeManager.writeValueAsString(document);
diff --git a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
index e5bd53b7b30..37b35c50f9a 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
@@ -27,27 +27,13 @@
 public class TestFunctions {
 
     public static ContractNegotiation createNegotiation(String id) {
-        return ContractNegotiation.Builder.newInstance()
-                .type(ContractNegotiation.Type.CONSUMER)
-                .id(id)
-                .contractAgreement(null)
-                .correlationId("corr-" + id)
-                .state(ContractNegotiationStates.REQUESTED.code())
-                .counterPartyAddress("consumer")
-                .counterPartyId("consumerId")
-                .protocol("ids-multipart")
+        return createNegotiationBuilder(id)
                 .build();
     }
 
     public static ContractNegotiation createNegotiation(String id, ContractAgreement agreement) {
-        return ContractNegotiation.Builder.newInstance()
-                .type(ContractNegotiation.Type.CONSUMER)
-                .id(id)
+        return createNegotiationBuilder(id)
                 .contractAgreement(agreement)
-                .correlationId("corr-" + id)
-                .counterPartyAddress("consumer")
-                .counterPartyId("consumerId")
-                .protocol("ids-multipart")
                 .build();
     }
 
@@ -62,10 +48,38 @@ public static ContractAgreement.Builder createContractBuilder(String id) {
                 .providerAgentId("provider")
                 .consumerAgentId("consumer")
                 .assetId(UUID.randomUUID().toString())
-                .policy(Policy.Builder.newInstance().build())
+                .policy(Policy.Builder.newInstance().build()))
                 .contractStartDate(Instant.now().getEpochSecond())
                 .contractEndDate(Instant.now().plus(1, ChronoUnit.DAYS).getEpochSecond())
                 .contractSigningDate(Instant.now().getEpochSecond());
     }
 
+    public static ContractNegotiation.Builder createNegotiationBuilder(String id) {
+        return ContractNegotiation.Builder.newInstance()
+                .type(ContractNegotiation.Type.CONSUMER)
+                .id(id)
+                .contractAgreement(null)
+                .correlationId("corr-" + id)
+                .state(ContractNegotiationStates.REQUESTED.code())
+                .counterPartyAddress("consumer")
+                .counterPartyId("consumerId")
+                .protocol("ids-multipart");
+    }
+
+    public static Policy createPolicy(String uid) {
+        return Policy.Builder.newInstance()
+                .id(uid)
+                .permission(Permission.Builder.newInstance()
+                        .target("")
+                        .action(Action.Builder.newInstance()
+                                .type("USE")
+                                .build())
+                        .constraint(AtomicConstraint.Builder.newInstance()
+                                .leftExpression(new LiteralExpression("foo"))
+                                .operator(Operator.EQ)
+                                .rightExpression(new LiteralExpression("bar"))
+                                .build())
+                        .build())
+                .build();
+    }
 }
diff --git a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
index ea9fd3b368f..7fec5535ecf 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
@@ -48,6 +48,7 @@
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.eclipse.dataspaceconnector.sql.SqlQueryExecutor.executeQuery;
 import static org.eclipse.dataspaceconnector.sql.contractnegotiation.TestFunctions.createContract;
+import static org.eclipse.dataspaceconnector.sql.contractnegotiation.TestFunctions.createContractBuilder;
 import static org.eclipse.dataspaceconnector.sql.contractnegotiation.TestFunctions.createNegotiation;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
@@ -418,16 +419,7 @@ void getNegotiationsWithAgreementOnAsset_negotiationWithAgreement() {
     @Test
     void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() {
         var assetId = UUID.randomUUID().toString();
-        var negotiation = ContractNegotiation.Builder.newInstance()
-                .type(ContractNegotiation.Type.CONSUMER)
-                .id("negotiation1")
-                .contractAgreement(null)
-                .correlationId("corr-negotiation1")
-                .state(ContractNegotiationStates.REQUESTED.code())
-                .counterPartyAddress("consumer")
-                .counterPartyId("consumerId")
-                .protocol("ids-multipart")
-                .build();
+        var negotiation = createNegotiation("negotiation1");
 
         store.save(negotiation);
 
@@ -438,6 +430,22 @@ void getNegotiationsWithAgreementOnAsset_negotiationWithoutAgreement() {
 
     }
 
+    @Test
+    void getNegotiationsWithAgreementOnAsset_multipleNegotiationsSameAsset() {
+        var assetId = UUID.randomUUID().toString();
+        var negotiation1 = createNegotiation("negotiation1", createContractBuilder("contract1").assetId(assetId).build());
+        var negotiation2 = createNegotiation("negotiation2", createContractBuilder("contract2").assetId(assetId).build());
+
+        store.save(negotiation1);
+        store.save(negotiation2);
+
+        var result = store.getNegotiationsWithAgreementOnAsset(assetId).collect(Collectors.toList());
+
+        assertThat(result).hasSize(2)
+                .extracting(ContractNegotiation::getId).containsExactlyInAnyOrder("negotiation1", "negotiation2");
+
+    }
+
     @Test
     @DisplayName("Verify that paging is used")
     void queryNegotiations_withAgreement() {

From f179074f4c9eecc1e19ed204998262a72e9e8c25 Mon Sep 17 00:00:00 2001
From: Paul Latzelsperger 
Date: Sun, 5 Jun 2022 11:46:14 +0200
Subject: [PATCH 4/5] moved statement out of store impl

---
 .../asset/service/AssetServiceImplTest.java         |  3 ++-
 .../store/ContractNegotiationStatements.java        |  6 ++++--
 .../store/PostgresStatements.java                   | 13 +++++++++++++
 .../store/SqlContractNegotiationStore.java          |  8 +-------
 .../sql/contractnegotiation/TestFunctions.java      | 10 +++++++---
 5 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java b/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
index ce1f6e2d8bc..bbf4ec97a18 100644
--- a/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
+++ b/extensions/api/data-management/asset/src/test/java/org/eclipse/dataspaceconnector/api/datamanagement/asset/service/AssetServiceImplTest.java
@@ -124,7 +124,8 @@ void delete_shouldNotDeleteIfAssetIsAlreadyPartOfAnAgreement() {
                         .providerAgentId(UUID.randomUUID().toString())
                         .consumerAgentId(UUID.randomUUID().toString())
                         .assetId(asset.getId())
-                        .policy(Policy.Builder.newInstance().build())                        .build())
+                        .policy(Policy.Builder.newInstance().build())
+                        .build())
                 .build();
         when(contractNegotiationStore.getNegotiationsWithAgreementOnAsset(any())).thenReturn(Stream.of(contractNegotiation));
 
diff --git a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/ContractNegotiationStatements.java b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/ContractNegotiationStatements.java
index f1dcd5b5538..c02a1f3b0a4 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/ContractNegotiationStatements.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/ContractNegotiationStatements.java
@@ -17,8 +17,8 @@
 import org.eclipse.dataspaceconnector.sql.lease.LeaseStatements;
 
 /**
- * Provides names for database columns, table names and statement templates.
- * Methods to compose statements must be overridden by implementors.
+ * Provides names for database columns, table names and statement templates. Methods to compose statements must be
+ * overridden by implementors.
  */
 public interface ContractNegotiationStatements extends LeaseStatements {
     String getFindTemplate();
@@ -47,6 +47,8 @@ public interface ContractNegotiationStatements extends LeaseStatements {
 
     String getUpdateAgreementTemplate();
 
+    String getNegotiationWitghAgreementOnAssetTemplate();
+
     @Override
     default String getLeasedByColumn() {
         return "leased_by";
diff --git a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/PostgresStatements.java b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/PostgresStatements.java
index 5c9bee50161..bfa72c4d0dd 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/PostgresStatements.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/PostgresStatements.java
@@ -110,6 +110,19 @@ public String getUpdateAgreementTemplate() {
                 getStartDateColumn(), getEndDateColumn(), getAssetIdColumn(), getPolicyIdColumn(), getContractAgreementIdColumn());
     }
 
+    @Override
+    public String getNegotiationWitghAgreementOnAssetTemplate() {
+        return format("SELECT * FROM %s\n" +
+                        "INNER JOIN %s eca on %s.%s = eca.%s\n" +
+                        "WHERE %s.%s in (SELECT %s\n" +
+                        "FROM %s\n" +
+                        "WHERE %s = ?);\n",
+                getContractNegotiationTable(), getContractAgreementTable(), getContractNegotiationTable(), getContractAgreementIdFkColumn(),
+                getContractAgreementIdColumn(),
+                getContractNegotiationTable(), getContractAgreementIdFkColumn(), getContractAgreementIdColumn(), getContractAgreementTable(),
+                getAssetIdColumn());
+    }
+
     @Override
     public String getDeleteLeaseTemplate() {
         return format("DELETE FROM %s WHERE %s=?", getLeaseTableName(), getLeaseIdColumn());
diff --git a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java
index 2df93b4239e..5c2930de93c 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStore.java
@@ -193,13 +193,7 @@ public void delete(String negotiationId) {
 
     @Override
     public Stream getNegotiationsWithAgreementOnAsset(String assetId) {
-        var statement = "\n" +
-                "SELECT *\n" +
-                "FROM edc_contract_negotiation\n" +
-                "INNER JOIN edc_contract_agreement eca on edc_contract_negotiation.contract_agreement_id = eca.agreement_id\n" +
-                "WHERE edc_contract_negotiation.contract_agreement_id in (SELECT agreement_id\n" +
-                "                                                         FROM edc_contract_agreement\n" +
-                "                                                         WHERE asset_id = ?);\n";
+        var statement = statements.getNegotiationWitghAgreementOnAssetTemplate();
 
         return transactionContext.execute(() -> {
             try (var connection = getConnection()) {
diff --git a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
index 37b35c50f9a..502fedb7cc2 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/TestFunctions.java
@@ -15,6 +15,11 @@
 
 package org.eclipse.dataspaceconnector.sql.contractnegotiation;
 
+import org.eclipse.dataspaceconnector.policy.model.Action;
+import org.eclipse.dataspaceconnector.policy.model.AtomicConstraint;
+import org.eclipse.dataspaceconnector.policy.model.LiteralExpression;
+import org.eclipse.dataspaceconnector.policy.model.Operator;
+import org.eclipse.dataspaceconnector.policy.model.Permission;
 import org.eclipse.dataspaceconnector.policy.model.Policy;
 import org.eclipse.dataspaceconnector.spi.types.domain.contract.agreement.ContractAgreement;
 import org.eclipse.dataspaceconnector.spi.types.domain.contract.negotiation.ContractNegotiation;
@@ -48,7 +53,7 @@ public static ContractAgreement.Builder createContractBuilder(String id) {
                 .providerAgentId("provider")
                 .consumerAgentId("consumer")
                 .assetId(UUID.randomUUID().toString())
-                .policy(Policy.Builder.newInstance().build()))
+                .policy(createPolicy())
                 .contractStartDate(Instant.now().getEpochSecond())
                 .contractEndDate(Instant.now().plus(1, ChronoUnit.DAYS).getEpochSecond())
                 .contractSigningDate(Instant.now().getEpochSecond());
@@ -66,9 +71,8 @@ public static ContractNegotiation.Builder createNegotiationBuilder(String id) {
                 .protocol("ids-multipart");
     }
 
-    public static Policy createPolicy(String uid) {
+    public static Policy createPolicy() {
         return Policy.Builder.newInstance()
-                .id(uid)
                 .permission(Permission.Builder.newInstance()
                         .target("")
                         .action(Action.Builder.newInstance()

From dbf03c4837ae17706adb7df09023ebbe7e4f18f2 Mon Sep 17 00:00:00 2001
From: Paul Latzelsperger 
Date: Tue, 7 Jun 2022 08:17:31 +0200
Subject: [PATCH 5/5] fixed test assertion

---
 .../store/SqlContractNegotiationStoreTest.java                  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
index 7fec5535ecf..b30f8fe30ee 100644
--- a/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
+++ b/extensions/sql/contract-negotiation-store-sql/src/test/java/org/eclipse/dataspaceconnector/sql/contractnegotiation/store/SqlContractNegotiationStoreTest.java
@@ -298,7 +298,7 @@ void update_addsAgreement_shouldPersist() {
                 .usingRecursiveFieldByFieldElementComparator()
                 .containsExactly(agreement);
 
-        assertThat(Objects.requireNonNull(store.find(negotiationId)).getContractAgreement()).isEqualTo(agreement);
+        assertThat(Objects.requireNonNull(store.find(negotiationId)).getContractAgreement()).usingRecursiveComparison().isEqualTo(agreement);
     }
 
     @Test