diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/ContractDefinitionApiExtension.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/ContractDefinitionApiExtension.java index 65e15bad596..52c5b0efaeb 100644 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/ContractDefinitionApiExtension.java +++ b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/ContractDefinitionApiExtension.java @@ -10,6 +10,7 @@ * Contributors: * Microsoft Corporation - initial API and implementation * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - improvements + * SAP SE - add private properties to contract definition * */ @@ -26,12 +27,14 @@ import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; import org.eclipse.edc.web.spi.WebService; import java.util.Map; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_TYPE; +import static org.eclipse.edc.spi.CoreConstants.JSON_LD; @Extension(value = ContractDefinitionApiExtension.NAME) public class ContractDefinitionApiExtension implements ServiceExtension { @@ -53,6 +56,9 @@ public class ContractDefinitionApiExtension implements ServiceExtension { @Inject JsonObjectValidatorRegistry validatorRegistry; + @Inject + private TypeManager typeManager; + @Override public String name() { return NAME; @@ -61,7 +67,8 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { var jsonFactory = Json.createBuilderFactory(Map.of()); - transformerRegistry.register(new JsonObjectFromContractDefinitionTransformer(jsonFactory)); + var mapper = typeManager.getMapper(JSON_LD); + transformerRegistry.register(new JsonObjectFromContractDefinitionTransformer(jsonFactory, mapper)); transformerRegistry.register(new JsonObjectToContractDefinitionTransformer()); validatorRegistry.register(CONTRACT_DEFINITION_TYPE, ContractDefinitionValidator.instance()); diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformer.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformer.java index 3fa67f45edc..e45ed6c229b 100644 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformer.java +++ b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformer.java @@ -9,11 +9,13 @@ * * Contributors: * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * SAP SE - add private properties to contract definition * */ package org.eclipse.edc.connector.api.management.contractdefinition.transform; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; @@ -31,11 +33,13 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; public class JsonObjectFromContractDefinitionTransformer extends AbstractJsonLdTransformer { + private final ObjectMapper mapper; private final JsonBuilderFactory jsonFactory; - public JsonObjectFromContractDefinitionTransformer(JsonBuilderFactory jsonFactory) { + public JsonObjectFromContractDefinitionTransformer(JsonBuilderFactory jsonFactory, ObjectMapper jsonLdMapper) { super(ContractDefinition.class, JsonObject.class); this.jsonFactory = jsonFactory; + this.mapper = jsonLdMapper; } @Override @@ -44,12 +48,19 @@ public JsonObjectFromContractDefinitionTransformer(JsonBuilderFactory jsonFactor .map(criterion -> context.transform(criterion, JsonObject.class)) .collect(toJsonArray()); - return jsonFactory.createObjectBuilder() + var builder = jsonFactory.createObjectBuilder() .add(ID, contractDefinition.getId()) .add(TYPE, CONTRACT_DEFINITION_TYPE) .add(CONTRACT_DEFINITION_ACCESSPOLICY_ID, contractDefinition.getAccessPolicyId()) .add(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, contractDefinition.getContractPolicyId()) - .add(CONTRACT_DEFINITION_ASSETS_SELECTOR, criteria) - .build(); + .add(CONTRACT_DEFINITION_ASSETS_SELECTOR, criteria); + + if (!contractDefinition.getPrivateProperties().isEmpty()) { + var privatePropBuilder = jsonFactory.createObjectBuilder(); + transformProperties(contractDefinition.getPrivateProperties(), privatePropBuilder, mapper, context); + builder.add(ContractDefinition.CONTRACT_DEFINITION_PRIVATE_PROPERTIES, privatePropBuilder); + } + + return builder.build(); } } diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformer.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformer.java index 2890f3e58a3..16f2501e604 100644 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformer.java +++ b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformer.java @@ -9,12 +9,14 @@ * * Contributors: * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * SAP SE - add private properties to contract definition * */ package org.eclipse.edc.connector.api.management.contractdefinition.transform; import jakarta.json.JsonObject; +import jakarta.json.JsonValue; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; import org.eclipse.edc.spi.query.Criterion; @@ -25,7 +27,7 @@ import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ASSETS_SELECTOR; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; - +import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_PRIVATE_PROPERTIES; public class JsonObjectToContractDefinitionTransformer extends AbstractJsonLdTransformer { @@ -36,19 +38,23 @@ public JsonObjectToContractDefinitionTransformer() { @Override public @Nullable ContractDefinition transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var builder = ContractDefinition.Builder.newInstance(); - builder.id(nodeId(object)); - - visitProperties(object, (key, value) -> { - switch (key) { - case CONTRACT_DEFINITION_ACCESSPOLICY_ID -> builder.accessPolicyId(transformString(value, context)); - case CONTRACT_DEFINITION_CONTRACTPOLICY_ID -> builder.contractPolicyId(transformString(value, context)); - case CONTRACT_DEFINITION_ASSETS_SELECTOR -> builder.assetsSelector(transformArray(value, Criterion.class, context)); - default -> { } - } - }); - + visitProperties(object, (s, jsonValue) -> transformProperties(s, jsonValue, builder, context)); return builderResult(builder::build, context); } + private void transformProperties(String key, JsonValue jsonValue, ContractDefinition.Builder builder, TransformerContext context) { + switch (key) { + case CONTRACT_DEFINITION_ACCESSPOLICY_ID -> builder.accessPolicyId(transformString(jsonValue, context)); + case CONTRACT_DEFINITION_CONTRACTPOLICY_ID -> builder.contractPolicyId(transformString(jsonValue, context)); + case CONTRACT_DEFINITION_ASSETS_SELECTOR -> builder.assetsSelector(transformArray(jsonValue, Criterion.class, context)); + case CONTRACT_DEFINITION_PRIVATE_PROPERTIES -> { + var props = jsonValue.asJsonArray().getJsonObject(0); + visitProperties(props, (k, val) -> transformProperties(k, val, builder, context)); + } + default -> { + builder.privateProperty(key, transformGenericProperty(jsonValue, context)); + } + } + } } diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformerTest.java b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformerTest.java index 97ca6202e94..a80423dfbcc 100644 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformerTest.java +++ b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectFromContractDefinitionTransformerTest.java @@ -9,6 +9,7 @@ * * Contributors: * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * SAP SE - add private properties to contract definition * */ @@ -29,9 +30,11 @@ import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ASSETS_SELECTOR; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; +import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_PRIVATE_PROPERTIES; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_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.util.JacksonJsonLd.createObjectMapper; import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -46,7 +49,7 @@ class JsonObjectFromContractDefinitionTransformerTest { private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of()); private final TransformerContext context = mock(TransformerContext.class); - private final JsonObjectFromContractDefinitionTransformer transformer = new JsonObjectFromContractDefinitionTransformer(jsonFactory); + private final JsonObjectFromContractDefinitionTransformer transformer = new JsonObjectFromContractDefinitionTransformer(jsonFactory, createObjectMapper()); @Test void transform() { @@ -72,4 +75,24 @@ void transform() { verify(context, never()).reportProblem(anyString()); } + + @Test + void transform_withPrivateProperties_simpleTypes() { + var criterionJson = jsonFactory.createObjectBuilder().build(); + when(context.transform(isA(Criterion.class), eq(JsonObject.class))).thenReturn(criterionJson); + var criterion = criterion("left", "=", "right"); + var contractDefinition = ContractDefinition.Builder.newInstance() + .id("id") + .accessPolicyId("accessPolicyId") + .contractPolicyId("contractPolicyId") + .assetsSelector(List.of(criterion)) + .privateProperty("some-key", "some-value") + .build(); + + var jsonObject = transformer.transform(contractDefinition, context); + + assertThat(jsonObject).isNotNull(); + assertThat(jsonObject.getJsonObject(CONTRACT_DEFINITION_PRIVATE_PROPERTIES).getJsonString("some-key").getString()).isEqualTo("some-value"); + } + } diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformerTest.java b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformerTest.java index b227d180181..eb9ab9e5b0b 100644 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformerTest.java +++ b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/connector/api/management/contractdefinition/transform/JsonObjectToContractDefinitionTransformerTest.java @@ -9,13 +9,17 @@ * * Contributors: * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * SAP SE - add private properties to contract definition * */ package org.eclipse.edc.connector.api.management.contractdefinition.transform; import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.Test; @@ -26,8 +30,14 @@ import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ASSETS_SELECTOR; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; +import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_PRIVATE_PROPERTIES; import static org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +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.VOCAB; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -40,6 +50,8 @@ class JsonObjectToContractDefinitionTransformerTest { private final JsonObjectToContractDefinitionTransformer transformer = new JsonObjectToContractDefinitionTransformer(); private final TransformerContext context = mock(TransformerContext.class); + private final TitaniumJsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + @Test void types() { assertThat(transformer.getInputType()).isEqualTo(JsonObject.class); @@ -84,4 +96,35 @@ void transform_whenNoAssetSelectorItShouldBeAnEmptyList() { verify(context, never()).transform(any(), any()); } + + @Test + void transform_withPrivateProperties() { + when(context.transform(any(), eq(Object.class))).thenReturn("test-val"); + var jsonObj = createObjectBuilder() + .add(CONTEXT, createContextBuilder().addNull(EDC_PREFIX).build()) + .add(ID, "some-contract-definition-id") + .add(TYPE, CONTRACT_DEFINITION_TYPE) + .add(CONTRACT_DEFINITION_ACCESSPOLICY_ID, "accessPolicyId") + .add(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, "contractPolicyId") + .add(CONTRACT_DEFINITION_PRIVATE_PROPERTIES, createArrayBuilder().add(createObjectBuilder().add("test-prop", "test-val").build()).build()) + .build(); + + jsonObj = expand(jsonObj); + var contractDefinition = transformer.transform(jsonObj, context); + + assertThat(contractDefinition.getPrivateProperties()) + .hasSize(1) + .containsEntry(EDC_NAMESPACE + "test-prop", "test-val"); + } + + private JsonObject expand(JsonObject jsonObject) { + return jsonLd.expand(jsonObject).orElseThrow(f -> new AssertionError(f.getFailureDetail())); + } + + private JsonObjectBuilder createContextBuilder() { + return createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .add(EDC_PREFIX, EDC_NAMESPACE); + } + } diff --git a/extensions/control-plane/store/sql/contract-definition-store-sql/docs/er.puml b/extensions/control-plane/store/sql/contract-definition-store-sql/docs/er.puml index 6d91b1efbe0..02a1320ce12 100644 --- a/extensions/control-plane/store/sql/contract-definition-store-sql/docs/er.puml +++ b/extensions/control-plane/store/sql/contract-definition-store-sql/docs/er.puml @@ -5,5 +5,6 @@ entity edc_contract_definitions { * access_policy: string <> * contract_policy: string <> * assets_selector: string <> + * private_properties: string <> } @enduml diff --git a/extensions/control-plane/store/sql/contract-definition-store-sql/docs/schema.sql b/extensions/control-plane/store/sql/contract-definition-store-sql/docs/schema.sql index 7751a6f1160..98a30b7e82e 100644 --- a/extensions/control-plane/store/sql/contract-definition-store-sql/docs/schema.sql +++ b/extensions/control-plane/store/sql/contract-definition-store-sql/docs/schema.sql @@ -10,6 +10,7 @@ -- Contributors: -- Daimler TSS GmbH - Initial SQL Query -- Microsoft Corporation - refactoring +-- SAP SE - add private properties to contract definition -- -- table: edc_contract_definitions @@ -21,5 +22,6 @@ CREATE TABLE IF NOT EXISTS edc_contract_definitions access_policy_id VARCHAR NOT NULL, contract_policy_id VARCHAR NOT NULL, assets_selector JSON NOT NULL, + private_properties JSON, PRIMARY KEY (contract_definition_id) ); diff --git a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/SqlContractDefinitionStore.java b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/SqlContractDefinitionStore.java index b3cc936b2b6..7f9e69c0c15 100644 --- a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/SqlContractDefinitionStore.java +++ b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/SqlContractDefinitionStore.java @@ -12,6 +12,7 @@ * Microsoft Corporation - refactoring, bugfixing * Fraunhofer Institute for Software and Systems Engineering - added method * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - improvements + * SAP SE - add private properties to contract definition * */ @@ -37,6 +38,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Stream; @@ -48,6 +50,9 @@ public class SqlContractDefinitionStore extends AbstractSqlStore implements Cont public static final TypeReference> CRITERION_LIST = new TypeReference<>() { }; + private static final TypeReference> PRIVATE_PROPERTIES_TYPE = new TypeReference<>() { + }; + public SqlContractDefinitionStore(DataSourceRegistry dataSourceRegistry, String dataSourceName, TransactionContext transactionContext, ContractDefinitionStatements statements, ObjectMapper objectMapper, QueryExecutor queryExecutor) { @@ -142,6 +147,7 @@ private ContractDefinition mapResultSet(ResultSet resultSet) throws Exception { .accessPolicyId(resultSet.getString(statements.getAccessPolicyIdColumn())) .contractPolicyId(resultSet.getString(statements.getContractPolicyIdColumn())) .assetsSelector(fromJson(resultSet.getString(statements.getAssetsSelectorColumn()), CRITERION_LIST)) + .privateProperties(fromJson(resultSet.getString(statements.getPrivatePropertiesColumn()), PRIVATE_PROPERTIES_TYPE)) .build(); } @@ -152,7 +158,8 @@ private void insertInternal(Connection connection, ContractDefinition definition definition.getAccessPolicyId(), definition.getContractPolicyId(), toJson(definition.getAssetsSelector()), - definition.getCreatedAt()); + definition.getCreatedAt(), + toJson(definition.getPrivateProperties())); }); } @@ -164,6 +171,7 @@ private void updateInternal(Connection connection, ContractDefinition definition definition.getContractPolicyId(), toJson(definition.getAssetsSelector()), definition.getCreatedAt(), + toJson(definition.getPrivateProperties()), definition.getId()); } @@ -182,5 +190,4 @@ private ContractDefinition findById(Connection connection, String id) { var sql = statements.getFindByTemplate(); return queryExecutor.single(connection, false, this::mapResultSet, sql, id); } - } diff --git a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/BaseSqlDialectStatements.java b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/BaseSqlDialectStatements.java index a752c2cf35e..c3515568e2d 100644 --- a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/BaseSqlDialectStatements.java +++ b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/BaseSqlDialectStatements.java @@ -40,11 +40,11 @@ public String getInsertTemplate() { .column(getContractPolicyIdColumn()) .jsonColumn(getAssetsSelectorColumn()) .column(getCreatedAtColumn()) + .jsonColumn(getPrivatePropertiesColumn()) .insertInto(getContractDefinitionTable()); } @Override - public String getCountTemplate() { return format("SELECT COUNT (%s) FROM %s WHERE %s = ?", getIdColumn(), @@ -60,6 +60,7 @@ public String getUpdateTemplate() { .column(getContractPolicyIdColumn()) .jsonColumn(getAssetsSelectorColumn()) .column(getCreatedAtColumn()) + .jsonColumn(getPrivatePropertiesColumn()) .update(getContractDefinitionTable(), getIdColumn()); } diff --git a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/ContractDefinitionStatements.java b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/ContractDefinitionStatements.java index 39b6329c73e..623062c6754 100644 --- a/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/ContractDefinitionStatements.java +++ b/extensions/control-plane/store/sql/contract-definition-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractdefinition/schema/ContractDefinitionStatements.java @@ -9,6 +9,7 @@ * * Contributors: * Microsoft Corporation - initial API and implementation + * SAP SE - add private properties to contract definition * */ @@ -50,6 +51,10 @@ default String getCreatedAtColumn() { return "created_at"; } + default String getPrivatePropertiesColumn() { + return "private_properties"; + } + String getDeleteByIdTemplate(); String getFindByTemplate(); diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinition.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinition.java index 1e28ab8735d..6812326b574 100644 --- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinition.java +++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinition.java @@ -9,6 +9,7 @@ * * Contributors: * Microsoft Corporation - initial API and implementation + * SAP SE - add private properties to contract definition * */ @@ -21,7 +22,9 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -45,11 +48,14 @@ public class ContractDefinition extends Entity { public static final String CONTRACT_DEFINITION_ACCESSPOLICY_ID = EDC_NAMESPACE + "accessPolicyId"; public static final String CONTRACT_DEFINITION_CONTRACTPOLICY_ID = EDC_NAMESPACE + "contractPolicyId"; public static final String CONTRACT_DEFINITION_ASSETS_SELECTOR = EDC_NAMESPACE + "assetsSelector"; + public static final String CONTRACT_DEFINITION_PRIVATE_PROPERTIES = EDC_NAMESPACE + "privateProperties"; private String accessPolicyId; private String contractPolicyId; private final List assetsSelector = new ArrayList<>(); + private final Map privateProperties = new HashMap<>(); + private ContractDefinition() { } @@ -68,9 +74,18 @@ public List getAssetsSelector() { return assetsSelector; } + @NotNull + public Map getPrivateProperties() { + return privateProperties; + } + + public Object getPrivateProperty(String key) { + return privateProperties.get(key); + } + @Override public int hashCode() { - return Objects.hash(id, accessPolicyId, contractPolicyId, assetsSelector); + return Objects.hash(id, accessPolicyId, contractPolicyId, assetsSelector, privateProperties); } @Override @@ -84,7 +99,8 @@ public boolean equals(Object o) { ContractDefinition that = (ContractDefinition) o; return Objects.equals(id, that.id) && Objects.equals(accessPolicyId, that.accessPolicyId) && Objects.equals(contractPolicyId, that.contractPolicyId) && - Objects.equals(assetsSelector, that.assetsSelector); + Objects.equals(assetsSelector, that.assetsSelector) && + Objects.equals(privateProperties, that.privateProperties); } @Override @@ -92,7 +108,8 @@ public String toString() { return "ContractDefinition{" + "accessPolicyId='" + accessPolicyId + '\'' + ", contractPolicyId='" + contractPolicyId + '\'' + - ", assetsSelector=" + assetsSelector + + ", assetsSelector=" + assetsSelector + '\'' + + ", privateProperties=" + privateProperties + '}'; } @@ -137,6 +154,17 @@ public Builder assetsSelector(List criteria) { return this; } + public Builder privateProperty(String key, Object value) { + entity.privateProperties.put(key, value); + return this; + } + + public Builder privateProperties(Map privateProperties) { + Objects.requireNonNull(privateProperties); + entity.privateProperties.putAll(privateProperties); + return this; + } + @Override public ContractDefinition build() { if (entity.getId() == null) { diff --git a/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinitionTest.java b/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinitionTest.java index 34036ed6486..2745318c49e 100644 --- a/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinitionTest.java +++ b/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/types/offer/ContractDefinitionTest.java @@ -9,6 +9,7 @@ * * Contributors: * Microsoft Corporation - Initial implementation + * SAP SE - add private properties to contract definition * */ @@ -19,6 +20,7 @@ import org.eclipse.edc.spi.types.TypeManager; import org.junit.jupiter.api.Test; +import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -34,6 +36,7 @@ void verifySerializeDeserialize() throws JsonProcessingException { .accessPolicyId(UUID.randomUUID().toString()) .contractPolicyId(UUID.randomUUID().toString()) .assetsSelectorCriterion(criterion("field", "=", "value")) + .privateProperties(Map.of("key1", "value2")) .build(); var serialized = mapper.writeValueAsString(definition); diff --git a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/ContractDefinitionStoreTestBase.java b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/ContractDefinitionStoreTestBase.java index 408ade9af5c..8082b8a4c17 100644 --- a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/ContractDefinitionStoreTestBase.java +++ b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/ContractDefinitionStoreTestBase.java @@ -14,11 +14,13 @@ * Microsoft Corporation - added tests * Fraunhofer Institute for Software and Systems Engineering - added tests * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - improvements + * SAP SE - SAP SE - add private properties to contract definition * */ package org.eclipse.edc.connector.contract.spi.testfixtures.offer.store; +import org.assertj.core.api.Assertions; import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.spi.query.Criterion; @@ -34,6 +36,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -124,6 +127,23 @@ void allExist() { assertThat(definitionsRetrieved).isNotNull().hasSize(definitionsCreated.size()); } + + @Test + @DisplayName("Save a single Contract Definition that doesn't already exist with private properties") + void doesNotExist_with_private_properties() { + var definition = createContractDefinition("id1", "policy", "contract", Map.of("key1", "value1", "key2", "value2")); + getContractDefinitionStore().save(definition); + + var definitions = getContractDefinitionStore().findAll(QuerySpec.max()) + .collect(Collectors.toList()); + + assertThat(definitions).hasSize(1); + assertThat(definitions.get(0)).usingRecursiveComparison().isEqualTo(definition); + + assertThat(definitions.get(0).getPrivateProperties()).hasSize(2); + assertThat(definitions.get(0).getPrivateProperties().get("key1")).usingRecursiveComparison().isEqualTo("value1"); + assertThat(definitions.get(0).getPrivateProperties().get("key2")).usingRecursiveComparison().isEqualTo("value2"); + } } @Nested @@ -159,6 +179,65 @@ void exists() { assertThat(definition.getContractPolicyId()).isEqualTo(definition2.getContractPolicyId()); }); } + + @Test + @DisplayName("Update contract definition that exists, adding a property") + void exists_addsProperty() { + var definition1 = createContractDefinition("id1", "policy1", "contract1"); + getContractDefinitionStore().save(definition1); + var definitions = getContractDefinitionStore().findAll(QuerySpec.none()).collect(Collectors.toList()); + assertThat(definitions).isNotNull().hasSize(1); + + definition1.getPrivateProperties().put("newKey", "newValue"); + var updated = getContractDefinitionStore().update(definition1); + Assertions.assertThat(updated).isNotNull(); + + var definitionFound = getContractDefinitionStore().findById("id1"); + + assertThat(definitionFound).isNotNull(); + assertThat(definitionFound).usingRecursiveComparison().isEqualTo(definition1); + } + + + @Test + @DisplayName("Update contract definition that exists, removing a property") + void exists_removesProperty() { + var definition1 = createContractDefinition("id1", "policy1", "contract1"); + definition1.getPrivateProperties().put("newKey", "newValue"); + getContractDefinitionStore().save(definition1); + var definitions = getContractDefinitionStore().findAll(QuerySpec.none()).collect(Collectors.toList()); + assertThat(definitions).isNotNull().hasSize(1); + + definition1.getPrivateProperties().remove("newKey"); + var updated = getContractDefinitionStore().update(definition1); + Assertions.assertThat(updated).isNotNull(); + + var definitionFound = getContractDefinitionStore().findById("id1"); + + assertThat(definitionFound).isNotNull(); + assertThat(definitionFound).usingRecursiveComparison().isEqualTo(definition1); + assertThat(definitionFound.getPrivateProperties()).doesNotContainKey("newKey"); + } + + @Test + @DisplayName("Update an Asset that exists, replacing a property") + void exists_replacingProperty() { + var definition1 = createContractDefinition("id1", "policy1", "contract1"); + definition1.getPrivateProperties().put("newKey", "originalValue"); + getContractDefinitionStore().save(definition1); + var definitions = getContractDefinitionStore().findAll(QuerySpec.none()).collect(Collectors.toList()); + assertThat(definitions).isNotNull().hasSize(1); + + definition1.getPrivateProperties().put("newKey", "newValue"); + var updated = getContractDefinitionStore().update(definition1); + Assertions.assertThat(updated).isNotNull(); + + var definitionFound = getContractDefinitionStore().findById("id1"); + + assertThat(definitionFound).isNotNull(); + assertThat(definitionFound).usingRecursiveComparison().isEqualTo(definition1); + assertThat(definitionFound.getPrivateProperties()).containsEntry("newKey", "newValue"); + } } @Nested @@ -296,7 +375,7 @@ void verifyFiltering() { @Test void shouldReturnEmpty_whenQueryByInvalidKey() { - var definitionsExpected = TestFunctions.createContractDefinitions(5); + var definitionsExpected = createContractDefinitions(5); saveContractDefinitions(definitionsExpected); var spec = QuerySpec.Builder.newInstance() @@ -398,6 +477,17 @@ void queryMultiple() { .usingRecursiveFieldByFieldElementComparator() .containsOnly(definitionsExpected.get(4)); } + + @Test + void shouldReturnAll_with_private_properties_whenNoFiltersApplied() { + var definition1 = createContractDefinition("definition1", "policyId", "contractId", Map.of("key1", "value1")); + getContractDefinitionStore().save(definition1); + var definition2 = createContractDefinition("definition2", "policyId", "contractId", Map.of("key2", "value2")); + getContractDefinitionStore().save(definition2); + + var definitionsRetrieved = getContractDefinitionStore().findAll(QuerySpec.max()); + assertThat(definitionsRetrieved).isNotNull().hasSize(2); + } } @Nested @@ -418,6 +508,17 @@ void findById_invalidId() { assertThat(getContractDefinitionStore().findById("invalid-id")).isNull(); } + @Test + void findById_with_private_properties() { + var id = "definitionId"; + var definition = createContractDefinition(id, "policyId", "contractId", Map.of("key1", "value1")); + getContractDefinitionStore().save(definition); + + var result = getContractDefinitionStore().findById(id); + + assertThat(result).isNotNull().isEqualTo(definition); + } + } @Nested @@ -458,6 +559,19 @@ void verifyStore() { assertThat(deletedDefinition.getContent()).isEqualTo(definition1); assertThat(getContractDefinitionStore().findAll(QuerySpec.max())).doesNotContain(definition1); } + + @Test + void shouldDelete_with_private_properties() { + var definitionExpected = createContractDefinition("test-id1", "policy1", "contract1", Map.of("key1", "value1")); + getContractDefinitionStore().save(definitionExpected); + assertThat(getContractDefinitionStore().findAll(QuerySpec.max())).hasSize(1); + + var deleted = getContractDefinitionStore().deleteById("test-id1"); + + assertThat(deleted.succeeded()).isTrue(); + assertThat(deleted.getContent()).isNotNull().usingRecursiveComparison().isEqualTo(definitionExpected); + assertThat(getContractDefinitionStore().findAll(QuerySpec.max())).isEmpty(); + } } protected abstract ContractDefinitionStore getContractDefinitionStore(); diff --git a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/TestFunctions.java b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/TestFunctions.java index 4a633758406..94113e256fd 100644 --- a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/TestFunctions.java +++ b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/offer/store/TestFunctions.java @@ -9,6 +9,7 @@ * * Contributors: * Microsoft Corporation - initial API and implementation + * SAP SE - SAP SE - add private properties to contract definition * */ @@ -17,6 +18,7 @@ import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -37,6 +39,15 @@ public static ContractDefinition createContractDefinition(String id, String acce .build(); } + public static ContractDefinition createContractDefinition(String id, String accessPolicyId, String contractPolicyId, Map privateProperties) { + return ContractDefinition.Builder.newInstance() + .id(id) + .accessPolicyId(accessPolicyId) + .contractPolicyId(contractPolicyId) + .privateProperties(privateProperties) + .build(); + } + public static List createContractDefinitions(int count) { return IntStream.range(0, count) .mapToObj(i -> createContractDefinition("id" + i, "policy" + i, "contract" + i))