Skip to content

Commit daee02a

Browse files
authored
feat: store claims in contract agreement (#5626)
1 parent 3633836 commit daee02a

19 files changed

Lines changed: 191 additions & 175 deletions

File tree

core/common/lib/json-ld-lib/src/main/resources/document/management-context-v2.jsonld

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@
215215
"consumerId": "edc:consumerId",
216216
"assetId": "edc:assetId",
217217
"agreementId": "edc:agreementId",
218-
"contractSigningDate": "edc:contractSigningDate"
218+
"contractSigningDate": "edc:contractSigningDate",
219+
"claims": "edc:claims"
219220
}
220221
},
221222
"TransferRequest": {

core/common/lib/sql-lib/src/main/java/org/eclipse/edc/sql/store/AbstractSqlStore.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ protected <T> String toJson(Object object, TypeReference<T> typeReference) {
7575
}
7676
}
7777

78+
protected <T> T fromJson(String json) {
79+
return fromJson(json, objectMapper.constructType(getTypeRef()));
80+
}
81+
7882
protected <T> T fromJson(String json, TypeReference<T> typeReference) {
7983
return fromJson(json, objectMapper.constructType(typeReference));
8084
}

core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImpl.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ public ServiceResult<ContractNegotiation> notifyAccepted(ParticipantContext part
163163
public ServiceResult<ContractNegotiation> notifyAgreed(ParticipantContext participantContext, ContractAgreementMessage message, TokenRepresentation tokenRepresentation) {
164164
return transactionContext.execute(() -> getNegotiation(participantContext, message.getProcessId())
165165
.compose(contractNegotiation -> verifyRequest(participantContext, tokenRepresentation, contractNegotiation.getLastContractOffer().getPolicy(), message)
166-
.compose(agent -> validateAgreed(message, agent, contractNegotiation)))
167-
.compose(cn -> onMessageDo(participantContext, message, contractNegotiation -> agreedAction(message, contractNegotiation))));
166+
.compose(agent -> validateAgreed(message, agent, contractNegotiation))
167+
)
168+
.compose(agent -> onMessageDo(participantContext, message, negotiation -> agreedAction(message, negotiation, agent))));
168169
}
169170

170171
@Override
@@ -266,16 +267,16 @@ private ServiceResult<ValidatableConsumerOffer> fetchValidatableOffer(Participan
266267
}
267268

268269
@NotNull
269-
private ServiceResult<ContractNegotiation> validateAgreed(ContractAgreementMessage message, ParticipantAgent agent, ContractNegotiation negotiation) {
270+
private ServiceResult<ParticipantAgent> validateAgreed(ContractAgreementMessage message, ParticipantAgent agent, ContractNegotiation negotiation) {
270271
var agreement = message.getContractAgreement();
271272
var result = validationService.validateConfirmed(agent, agreement, negotiation.getLastContractOffer());
272273
if (result.failed()) {
273274
var msg = "Contract agreement received. Validation failed: " + result.getFailureDetail();
274275
monitor.debug("[Consumer] " + msg);
275276
return ServiceResult.badRequest(msg);
276-
} else {
277-
return ServiceResult.success(negotiation);
278277
}
278+
279+
return ServiceResult.success(agent);
279280
}
280281

281282
@NotNull
@@ -302,11 +303,12 @@ private ServiceResult<ContractNegotiation> acceptedAction(ContractNegotiationEve
302303
}
303304

304305
@NotNull
305-
private ServiceResult<ContractNegotiation> agreedAction(ContractAgreementMessage message, ContractNegotiation negotiation) {
306+
private ServiceResult<ContractNegotiation> agreedAction(ContractAgreementMessage message, ContractNegotiation negotiation, ParticipantAgent agent) {
306307
if (negotiation.getType().equals(CONSUMER) && negotiation.canBeAgreedConsumer()) {
307308

308309
var agreement = message.getContractAgreement().toBuilder()
309310
.participantContextId(negotiation.getParticipantContextId())
311+
.claims(agent.getClaims())
310312
.build();
311313

312314
negotiation.protocolMessageReceived(message.getId());

core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
5050
import org.eclipse.edc.transaction.spi.NoopTransactionContext;
5151
import org.eclipse.edc.transaction.spi.TransactionContext;
52+
import org.jspecify.annotations.NonNull;
5253
import org.junit.jupiter.api.BeforeEach;
5354
import org.junit.jupiter.api.Nested;
5455
import org.junit.jupiter.api.Test;
@@ -57,14 +58,17 @@
5758
import org.junit.jupiter.params.provider.Arguments;
5859
import org.junit.jupiter.params.provider.ArgumentsProvider;
5960
import org.junit.jupiter.params.provider.ArgumentsSource;
61+
import org.junit.jupiter.params.support.ParameterDeclarations;
6062
import org.mockito.ArgumentCaptor;
6163

6264
import java.time.Instant;
63-
import java.util.Collections;
65+
import java.util.Map;
6466
import java.util.UUID;
6567
import java.util.stream.Stream;
6668

69+
import static java.util.Collections.emptyMap;
6770
import static org.assertj.core.api.Assertions.assertThat;
71+
import static org.assertj.core.api.Assertions.entry;
6872
import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.CONSUMER;
6973
import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER;
7074
import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.ACCEPTED;
@@ -150,44 +154,55 @@ void notifyAccepted_shouldTransitionToAccepted() {
150154
verify(transactionContext, atLeastOnce()).execute(any(TransactionContext.ResultTransactionBlock.class));
151155
}
152156

153-
@Test
154-
void notifyAgreed_shouldTransitionToAgreed() {
155-
var negotiationConsumerRequested = createContractNegotiationRequested();
156-
var participantAgent = participantAgent();
157-
var tokenRepresentation = tokenRepresentation();
157+
@Nested
158+
class NotifyAgreed {
158159

159-
var contractAgreement = ContractAgreement.Builder.newInstance()
160-
.providerId("providerId")
161-
.consumerId("consumerId")
162-
.assetId("assetId")
163-
.policy(Policy.Builder.newInstance().build())
164-
.participantContextId("participantContextId")
165-
.build();
166-
var message = ContractAgreementMessage.Builder.newInstance()
167-
.protocol("protocol")
168-
.counterPartyAddress("http://any")
169-
.processId("processId")
170-
.consumerPid("consumerPid")
171-
.providerPid("providerPid")
172-
.contractAgreement(contractAgreement)
173-
.build();
160+
@Test
161+
void shouldTransitionToAgreed() {
162+
var negotiationConsumerRequested = createContractNegotiationRequested();
163+
var participantAgent = new ParticipantAgent("counterPartyId", Map.of("claim", "value"), emptyMap());
164+
var tokenRepresentation = tokenRepresentation();
174165

175-
when(protocolTokenValidator.verify(eq(participantContext), eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent));
176-
when(store.findById(any())).thenReturn(negotiationConsumerRequested);
177-
when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiationConsumerRequested));
178-
when(validationService.validateConfirmed(eq(participantAgent), eq(contractAgreement), any(ContractOffer.class))).thenReturn(Result.success());
166+
var contractAgreement = ContractAgreement.Builder.newInstance()
167+
.providerId("providerId")
168+
.consumerId("consumerId")
169+
.assetId("assetId")
170+
.policy(Policy.Builder.newInstance().build())
171+
.participantContextId("participantContextId")
172+
.build();
173+
var message = ContractAgreementMessage.Builder.newInstance()
174+
.protocol("protocol")
175+
.counterPartyAddress("http://any")
176+
.processId("processId")
177+
.consumerPid("consumerPid")
178+
.providerPid("providerPid")
179+
.contractAgreement(contractAgreement)
180+
.build();
179181

180-
var result = service.notifyAgreed(participantContext, message, tokenRepresentation);
182+
when(protocolTokenValidator.verify(eq(participantContext), eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent));
183+
when(store.findById(any())).thenReturn(negotiationConsumerRequested);
184+
when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiationConsumerRequested));
185+
when(validationService.validateConfirmed(eq(participantAgent), eq(contractAgreement), any(ContractOffer.class))).thenReturn(Result.success());
186+
187+
var result = service.notifyAgreed(participantContext, message, tokenRepresentation);
188+
189+
assertThat(result).isSucceeded();
190+
verify(store).findById("processId");
191+
verify(store).findByIdAndLease("processId");
192+
ArgumentCaptor<ContractNegotiation> negotiationCaptor = ArgumentCaptor.forClass(ContractNegotiation.class);
193+
verify(store).save(negotiationCaptor.capture());
194+
ContractNegotiation storedNegotiation = negotiationCaptor.getValue();
195+
assertThat(storedNegotiation.stateAsString()).isEqualTo(AGREED.name());
196+
assertThat(storedNegotiation.getContractAgreement()).satisfies(storedAgreement -> {
197+
assertThat(storedAgreement.getProviderId()).isEqualTo("providerId");
198+
assertThat(storedAgreement.getConsumerId()).isEqualTo("consumerId");
199+
assertThat(storedAgreement.getClaims()).hasSize(1).containsExactly(entry("claim", "value"));
200+
});
201+
verify(validationService).validateConfirmed(eq(participantAgent), eq(contractAgreement), any(ContractOffer.class));
202+
verify(listener).agreed(any());
203+
verify(transactionContext, atLeastOnce()).execute(any(TransactionContext.ResultTransactionBlock.class));
204+
}
181205

182-
assertThat(result).isSucceeded();
183-
verify(store).findById("processId");
184-
verify(store).findByIdAndLease("processId");
185-
verify(store).save(argThat(negotiation ->
186-
negotiation.getState() == AGREED.code() && negotiation.getContractAgreement().equals(contractAgreement)
187-
));
188-
verify(validationService).validateConfirmed(eq(participantAgent), eq(contractAgreement), any(ContractOffer.class));
189-
verify(listener).agreed(any());
190-
verify(transactionContext, atLeastOnce()).execute(any(TransactionContext.ResultTransactionBlock.class));
191206
}
192207

193208
@Test
@@ -433,7 +448,7 @@ <M extends RemoteMessage> void notify_shouldReturnNotFound_whenParticipantContex
433448

434449

435450
private ParticipantAgent participantAgent() {
436-
return new ParticipantAgent("counterPartyId", Collections.emptyMap(), Collections.emptyMap());
451+
return new ParticipantAgent("counterPartyId", emptyMap(), emptyMap());
437452
}
438453

439454
private ContractNegotiation createContractNegotiationRequested() {
@@ -501,7 +516,7 @@ static ContractDefinition createContractDefinition() {
501516
private static class NotifyArguments implements ArgumentsProvider {
502517

503518
@Override
504-
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
519+
public @NonNull Stream<? extends Arguments> provideArguments(@NonNull ParameterDeclarations parameters, @NonNull ExtensionContext context) {
505520
MethodCall<ContractRequestMessage> requested = ContractNegotiationProtocolService::notifyRequested;
506521
MethodCall<ContractOfferMessage> offered = ContractNegotiationProtocolService::notifyOffered;
507522
MethodCall<ContractAgreementMessage> agreed = ContractNegotiationProtocolService::notifyAgreed;

core/control-plane/control-plane-transform/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030
api(project(":spi:control-plane:catalog-spi"))
3131

3232
testImplementation(project(":core:common:junit-base"));
33+
testImplementation(project(":core:common:lib:json-lib"))
3334
testImplementation(project(":core:common:lib:json-ld-lib"))
3435
testImplementation(project(":core:common:lib:transform-lib"))
3536
}

core/control-plane/control-plane-transform/src/main/java/org/eclipse/edc/connector/controlplane/transform/edc/contractagreement/from/JsonObjectFromContractAgreementTransformer.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@
1414

1515
package org.eclipse.edc.connector.controlplane.transform.edc.contractagreement.from;
1616

17+
import com.fasterxml.jackson.databind.ObjectMapper;
1718
import jakarta.json.JsonBuilderFactory;
1819
import jakarta.json.JsonObject;
20+
import jakarta.json.JsonObjectBuilder;
1921
import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement;
2022
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
2123
import org.eclipse.edc.transform.spi.TransformerContext;
2224
import org.jetbrains.annotations.NotNull;
2325
import org.jetbrains.annotations.Nullable;
2426

27+
import java.util.function.Supplier;
28+
2529
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_ASSET_ID;
30+
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_CLAIMS;
2631
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_CONSUMER_ID;
2732
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_ID;
2833
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_POLICY;
@@ -34,23 +39,33 @@
3439

3540
public class JsonObjectFromContractAgreementTransformer extends AbstractJsonLdTransformer<ContractAgreement, JsonObject> {
3641
private final JsonBuilderFactory jsonFactory;
42+
private final Supplier<ObjectMapper> objectMapperSupplier;
3743

38-
public JsonObjectFromContractAgreementTransformer(JsonBuilderFactory jsonFactory) {
44+
public JsonObjectFromContractAgreementTransformer(JsonBuilderFactory jsonFactory, Supplier<ObjectMapper> objectMapperSupplier) {
3945
super(ContractAgreement.class, JsonObject.class);
4046
this.jsonFactory = jsonFactory;
47+
this.objectMapperSupplier = objectMapperSupplier;
4148
}
4249

4350
@Override
4451
public @Nullable JsonObject transform(@NotNull ContractAgreement agreement, @NotNull TransformerContext context) {
45-
return jsonFactory.createObjectBuilder()
52+
JsonObjectBuilder builder = jsonFactory.createObjectBuilder()
4653
.add(TYPE, CONTRACT_AGREEMENT_TYPE)
4754
.add(ID, agreement.getId())
4855
.add(CONTRACT_AGREEMENT_ASSET_ID, agreement.getAssetId())
4956
.add(CONTRACT_AGREEMENT_ID, agreement.getAgreementId())
5057
.add(CONTRACT_AGREEMENT_POLICY, context.transform(agreement.getPolicy(), JsonObject.class))
5158
.add(CONTRACT_AGREEMENT_SIGNING_DATE, agreement.getContractSigningDate())
5259
.add(CONTRACT_AGREEMENT_CONSUMER_ID, agreement.getConsumerId())
53-
.add(CONTRACT_AGREEMENT_PROVIDER_ID, agreement.getProviderId())
60+
.add(CONTRACT_AGREEMENT_PROVIDER_ID, agreement.getProviderId());
61+
62+
if (agreement.getClaims() != null) {
63+
var propBuilder = jsonFactory.createObjectBuilder();
64+
transformProperties(agreement.getClaims(), propBuilder, objectMapperSupplier.get(), context);
65+
builder.add(CONTRACT_AGREEMENT_CLAIMS, propBuilder);
66+
}
67+
68+
return builder
5469
.build();
5570
}
5671
}

core/control-plane/control-plane-transform/src/test/java/org/eclipse/edc/connector/controlplane/transform/edc/contractagreement/from/JsonObjectFromContractAgreementTransformerTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import jakarta.json.Json;
1818
import jakarta.json.JsonObject;
1919
import jakarta.json.JsonString;
20+
import jakarta.json.JsonValue;
2021
import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement;
22+
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
2123
import org.eclipse.edc.policy.model.Policy;
2224
import org.eclipse.edc.transform.spi.TransformerContext;
2325
import org.junit.jupiter.api.BeforeEach;
@@ -26,12 +28,15 @@
2628
import java.util.Map;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.entry;
2932
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_ASSET_ID;
33+
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_CLAIMS;
3034
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_CONSUMER_ID;
3135
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_ID;
3236
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_POLICY;
3337
import static org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement.CONTRACT_AGREEMENT_PROVIDER_ID;
3438
import static org.mockito.ArgumentMatchers.any;
39+
import static org.mockito.ArgumentMatchers.anyString;
3540
import static org.mockito.ArgumentMatchers.eq;
3641
import static org.mockito.Mockito.mock;
3742
import static org.mockito.Mockito.when;
@@ -42,7 +47,7 @@ class JsonObjectFromContractAgreementTransformerTest {
4247

4348
@BeforeEach
4449
void setUp() {
45-
transformer = new JsonObjectFromContractAgreementTransformer(Json.createBuilderFactory(Map.of()));
50+
transformer = new JsonObjectFromContractAgreementTransformer(Json.createBuilderFactory(Map.of()), JacksonJsonLd::createObjectMapper);
4651
}
4752

4853
@Test
@@ -54,9 +59,11 @@ void transform() {
5459
.assetId("test-asset")
5560
.agreementId("agreement-id")
5661
.policy(Policy.Builder.newInstance().build())
62+
.claims(Map.of("key", "value"))
5763
.build();
5864
var context = mock(TransformerContext.class);
5965
when(context.transform(any(Policy.class), eq(JsonObject.class))).thenReturn(Json.createObjectBuilder().build());
66+
when(context.transform(anyString(), eq(JsonValue.class))).thenAnswer(i -> Json.createValue(i.getArgument(0, String.class)));
6067

6168
var result = transformer.transform(agreement, context);
6269

@@ -66,5 +73,6 @@ void transform() {
6673
assertThat(result.getJsonString(CONTRACT_AGREEMENT_CONSUMER_ID)).extracting(JsonString::getString).isEqualTo("test-consumer");
6774
assertThat(result.getJsonString(CONTRACT_AGREEMENT_ID)).extracting(JsonString::getString).isEqualTo("agreement-id");
6875
assertThat(result.getJsonObject(CONTRACT_AGREEMENT_POLICY)).isNotNull();
76+
assertThat(result.getJsonObject(CONTRACT_AGREEMENT_CLAIMS)).isNotNull().hasSize(1).contains(entry("key", Json.createValue("value")));
6977
}
7078
}

extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public void initialize(ServiceExtensionContext context) {
138138
var managementApiTransformerRegistry = transformerRegistry.forContext(MANAGEMENT_API_CONTEXT);
139139

140140
var factory = Json.createBuilderFactory(Map.of());
141-
managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory));
141+
managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory, () -> typeManager.getMapper(JSON_LD)));
142142
managementApiTransformerRegistry.register(new JsonObjectFromDataAddressTransformer(factory, typeManager, JSON_LD));
143143
managementApiTransformerRegistry.register(new JsonObjectFromDataplaneMetadataTransformer(factory, () -> typeManager.getMapper(JSON_LD)));
144144
managementApiTransformerRegistry.register(new JsonObjectFromAssetTransformer(factory, typeManager, JSON_LD));

extensions/control-plane/api/management-api-v5/contract-negotiation-api-v5/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiV5Extension.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public void initialize(ServiceExtensionContext context) {
9696
managementApiTransformerRegistry.register(new JsonObjectToTerminateNegotiationTransformer());
9797
managementApiTransformerRegistry.register(new JsonObjectFromContractNegotiationTransformer(factory));
9898
managementApiTransformerRegistry.register(new JsonObjectFromNegotiationStateTransformer(factory));
99-
managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory));
99+
managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory, () -> typeManager.getMapper(JSON_LD)));
100100

101101
authorizationService.addLookupFunction(ContractNegotiation.class, this::findContractNegotiation);
102102

0 commit comments

Comments
 (0)