Skip to content

Commit

Permalink
fix: transform policy to contract offer (#3626)
Browse files Browse the repository at this point in the history
* fix: transform policy to contract offer

* pr remarks
  • Loading branch information
ndr-brt committed Nov 20, 2023
1 parent 8fb17e3 commit 6012c7a
Show file tree
Hide file tree
Showing 17 changed files with 332 additions and 145 deletions.
Expand Up @@ -157,7 +157,11 @@ public JsonObject visitPolicy(Policy policy) {
.add(ODRL_PROHIBITION_ATTRIBUTE, prohibitionsBuilder)
.add(ODRL_OBLIGATION_ATTRIBUTE, obligationsBuilder);

Optional.ofNullable(policy.getTarget()).ifPresent(it -> builder.add(ODRL_TARGET_ATTRIBUTE, it));
Optional.ofNullable(policy.getTarget())
.ifPresent(target -> builder.add(
ODRL_TARGET_ATTRIBUTE,
jsonFactory.createArrayBuilder().add(jsonFactory.createObjectBuilder().add(ID, target)))
);

return builder.build();
}
Expand Down
Expand Up @@ -17,6 +17,8 @@
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import org.eclipse.edc.policy.model.Action;
import org.eclipse.edc.policy.model.AndConstraint;
import org.eclipse.edc.policy.model.AtomicConstraint;
Expand All @@ -36,6 +38,7 @@
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
Expand Down Expand Up @@ -90,7 +93,14 @@ void transform_policyWithAllRuleTypes_returnJsonObject() {

assertThat(result).isNotNull();
assertThat(result.getJsonString(TYPE).getString()).isEqualTo(OdrlNamespace.ODRL_SCHEMA + "Set");
assertThat(result.getString(ODRL_TARGET_ATTRIBUTE)).isEqualTo("target");
assertThat(result.get(ODRL_TARGET_ATTRIBUTE))
.isNotNull()
.isInstanceOf(JsonArray.class)
.extracting(JsonValue::asJsonArray)
.matches(it -> !it.isEmpty())
.asList().first()
.asInstanceOf(type(JsonObject.class))
.matches(it -> it.getString(ID).equals("target"));

assertThat(result.get(ODRL_PERMISSION_ATTRIBUTE))
.isNotNull()
Expand Down
Expand Up @@ -21,6 +21,7 @@
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectFromContractNegotiationTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectFromNegotiationStateTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractOfferDescriptionTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractOfferTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractRequestTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToTerminateNegotiationCommandTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.validation.ContractRequestValidator;
Expand Down Expand Up @@ -68,7 +69,8 @@ public void initialize(ServiceExtensionContext context) {
var factory = Json.createBuilderFactory(Map.of());
var monitor = context.getMonitor();

transformerRegistry.register(new JsonObjectToContractRequestTransformer(monitor));
transformerRegistry.register(new JsonObjectToContractRequestTransformer());
transformerRegistry.register(new JsonObjectToContractOfferTransformer());
transformerRegistry.register(new JsonObjectToContractOfferDescriptionTransformer());
transformerRegistry.register(new JsonObjectToTerminateNegotiationCommandTransformer());
transformerRegistry.register(new JsonObjectFromContractNegotiationTransformer(factory));
Expand Down
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.management.contractnegotiation.transform;

import jakarta.json.JsonObject;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.types.domain.offer.ContractOffer;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class JsonObjectToContractOfferTransformer extends AbstractJsonLdTransformer<JsonObject, ContractOffer> {

public JsonObjectToContractOfferTransformer() {
super(JsonObject.class, ContractOffer.class);
}

@Override
public @Nullable ContractOffer transform(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) {
var policy = context.transform(jsonObject, Policy.class);
if (policy == null) {
return null;
}
var id = nodeId(jsonObject);
return ContractOffer.Builder.newInstance()
.id(id)
.assetId(policy.getTarget())
.policy(policy)
.build();
}
}
Expand Up @@ -18,16 +18,12 @@
import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.types.domain.callback.CallbackAddress;
import org.eclipse.edc.spi.types.domain.offer.ContractOffer;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;

import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CALLBACK_ADDRESSES;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CONNECTOR_ADDRESS;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CONTRACT_REQUEST_COUNTER_PARTY_ADDRESS;
Expand All @@ -38,11 +34,8 @@

public class JsonObjectToContractRequestTransformer extends AbstractJsonLdTransformer<JsonObject, ContractRequest> {

private final Monitor monitor;

public JsonObjectToContractRequestTransformer(Monitor monitor) {
public JsonObjectToContractRequestTransformer() {
super(JsonObject.class, ContractRequest.class);
this.monitor = monitor;
}

@Override
Expand All @@ -52,31 +45,33 @@ public JsonObjectToContractRequestTransformer(Monitor monitor) {
.counterPartyAddress(counterPartyAddressOrConnectorAddress(jsonObject, context))
.protocol(transformString(jsonObject.get(PROTOCOL), context));

var policyJson = jsonObject.get(POLICY);
if (policyJson != null) {
var policy = transformObject(jsonObject.get(POLICY), Policy.class, context);
contractRequestBuilder.policy(policy);
contractRequestBuilder.contractOffer(contractOffer(jsonObject, context));

var callbackAddress = jsonObject.get(CALLBACK_ADDRESSES);
if (callbackAddress != null) {
contractRequestBuilder.callbackAddresses(transformArray(callbackAddress, CallbackAddress.class, context));
}

return contractRequestBuilder.build();
}

private ContractOffer contractOffer(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) {
var policy = jsonObject.get(POLICY);
if (policy != null) {
return transformObject(policy, ContractOffer.class, context);
}

var offerJson = jsonObject.get(OFFER);
if (offerJson != null) {
var contractOfferDescription = transformObject(jsonObject.get(OFFER), ContractOfferDescription.class, context);
var contractOffer = ContractOffer.Builder.newInstance()
return ContractOffer.Builder.newInstance()
.id(contractOfferDescription.getOfferId())
.assetId(contractOfferDescription.getAssetId())
.policy(contractOfferDescription.getPolicy())
.build();
contractRequestBuilder.contractOffer(contractOffer);
}

var callbackAddress = jsonObject.get(CALLBACK_ADDRESSES);
if (callbackAddress != null) {
var addresses = new ArrayList<CallbackAddress>();
transformArrayOrObject(callbackAddress, CallbackAddress.class, addresses::add, context);
contractRequestBuilder.callbackAddresses(addresses);
}

return contractRequestBuilder.build();
return null;
}

private String getProviderId(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) {
Expand Down
Expand Up @@ -19,11 +19,11 @@
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.validator.jsonobject.JsonLdPath;
import org.eclipse.edc.validator.jsonobject.JsonObjectValidator;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryIdNotBlank;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryObject;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryValue;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;

import static java.lang.String.format;
import static org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription.ASSET_ID;
Expand All @@ -34,6 +34,7 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.OFFER;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.POLICY;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.PROTOCOL;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_TARGET_ATTRIBUTE;

public class ContractRequestValidator {

Expand All @@ -60,12 +61,15 @@ public ValidationResult validate(JsonObject input) {
).build().validate(input);
}

var policyValidity = new MandatoryObject(path.append(POLICY)).validate(input);
if (policyValidity.succeeded()) {
return ValidationResult.success();
}
var validator = JsonObjectValidator.newValidator()
.verify(POLICY, MandatoryObject::new)
.verifyObject(POLICY, builder -> builder
.verifyId(MandatoryIdNotBlank::new)
.verify(ODRL_TARGET_ATTRIBUTE, MandatoryObject::new)
.verifyObject(ODRL_TARGET_ATTRIBUTE, b -> b.verifyId(MandatoryIdNotBlank::new)))
.build();

return ValidationResult.failure(Violation.violation(format("'%s' or '%s' must not be empty", OFFER, POLICY), path.toString()));
return validator.validate(input);
}
}

Expand Down
Expand Up @@ -327,7 +327,7 @@ void getSingleContractNegotiationAgreement_whenNoneFound() {
}

@Test
void initiate_with_contractOffer() {
void initiate() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());
var contractNegotiation = createContractNegotiation("cn1");
var responseBody = createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build();
Expand Down Expand Up @@ -363,39 +363,6 @@ void initiate_with_contractOffer() {
verifyNoMoreInteractions(transformerRegistry, service);
}

@Test
void initiate_with_policy() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());
var contractNegotiation = createContractNegotiation("cn1");
var responseBody = createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build();

when(transformerRegistry.transform(any(JsonObject.class), eq(ContractRequest.class))).thenReturn(Result.success(
ContractRequest.Builder.newInstance()
.protocol("test-protocol")
.providerId("test-provider-id")
.counterPartyAddress("test-cb")
.policy(Policy.Builder.newInstance().build())
.build()));

when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(responseBody));
when(service.initiateNegotiation(any(ContractRequest.class))).thenReturn(contractNegotiation);

when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody));

baseRequest()
.contentType(JSON)
.body(createObjectBuilder().build())
.post()
.then()
.statusCode(200)
.body(ID, is(contractNegotiation.getId()));

verify(service).initiateNegotiation(any());
verify(transformerRegistry).transform(any(JsonObject.class), eq(ContractRequest.class));
verify(transformerRegistry).transform(any(IdResponse.class), eq(JsonObject.class));
verifyNoMoreInteractions(transformerRegistry, service);
}

@Test
void initiate_shouldReturnBadRequest_whenValidationFails() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.failure(violation("error", "path")));
Expand Down
Expand Up @@ -19,6 +19,7 @@
import jakarta.json.JsonObject;
import org.eclipse.edc.api.transformer.JsonObjectToCallbackAddressTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractOfferDescriptionTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractOfferTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractRequestTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToTerminateNegotiationCommandTransformer;
import org.eclipse.edc.connector.api.management.contractnegotiation.validation.ContractRequestValidator;
Expand Down Expand Up @@ -56,7 +57,8 @@ class ContractNegotiationApiTest {

@BeforeEach
void setUp() {
transformer.register(new JsonObjectToContractRequestTransformer(monitor));
transformer.register(new JsonObjectToContractRequestTransformer());
transformer.register(new JsonObjectToContractOfferTransformer());
transformer.register(new JsonObjectToContractOfferDescriptionTransformer());
transformer.register(new JsonObjectToCallbackAddressTransformer());
transformer.register(new JsonObjectToTerminateNegotiationCommandTransformer());
Expand Down
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.management.contractnegotiation.transform;

import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static jakarta.json.Json.createObjectBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OBLIGATION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_SET;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PROHIBITION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_TARGET_ATTRIBUTE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JsonObjectToContractOfferTransformerTest {

private final JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class));
private final TransformerContext context = mock();
private JsonObjectToContractOfferTransformer transformer;

@BeforeEach
void setUp() {
transformer = new JsonObjectToContractOfferTransformer();
}

@Test
void transform() {
var offerPolicy = createObjectBuilder()
.add(TYPE, ODRL_POLICY_TYPE_SET)
.add(ID, "test-offer-id")
.add(ODRL_TARGET_ATTRIBUTE, "test-asset")
.add(ODRL_PERMISSION_ATTRIBUTE, getJsonObject("permission"))
.add(ODRL_PROHIBITION_ATTRIBUTE, getJsonObject("prohibition"))
.add(ODRL_OBLIGATION_ATTRIBUTE, getJsonObject("duty"))
.build();

var policy = Policy.Builder.newInstance().target("test-asset").build();
when(context.transform(any(JsonValue.class), eq(Policy.class))).thenReturn(policy);

var result = transformer.transform(jsonLd.expand(offerPolicy).getContent(), context);

assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo("test-offer-id");
assertThat(result.getAssetId()).isEqualTo("test-asset");
}

@Test
void shouldReturnNull_whenPolicyIsNull() {
when(context.transform(any(JsonValue.class), eq(Policy.class))).thenReturn(null);

var result = transformer.transform(createObjectBuilder().build(), context);

assertThat(result).isNull();
}

private JsonObject getJsonObject(String type) {
return createObjectBuilder()
.add(TYPE, type)
.build();
}
}

0 comments on commit 6012c7a

Please sign in to comment.