diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e4042584..6a78037f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,8 @@ All notable changes to this project will be documented in this file. ### Minor Changes - UI API Wrapper Model: - - Opinionated Policy Model and Mappers for EDC UI and Broker Server Extension + - UiPolicy + PolicyMapper for EDC UI and Broker Server + - UiAsset + AssetMapper for EDC UI and Broker Server - UI API Wrapper Endpoints: - Asset Page - Create Asset diff --git a/extensions/wrapper/client/build.gradle.kts b/extensions/wrapper/client/build.gradle.kts index da7569e62..ed8781609 100644 --- a/extensions/wrapper/client/build.gradle.kts +++ b/extensions/wrapper/client/build.gradle.kts @@ -63,7 +63,7 @@ dependencies { testImplementation("${jettyGroup}:jetty-util:${jettyVersion}") testImplementation("${jettyGroup}:jetty-webapp:${jettyVersion}") - testImplementation("${edcGroup}:json-ld-spi:${edcVersion}") + testImplementation("${edcGroup}:json-ld:${edcVersion}") testImplementation("${edcGroup}:dsp-http-spi:${edcVersion}") testImplementation("${edcGroup}:dsp-api-configuration:${edcVersion}") testImplementation(project(":extensions:wrapper:wrapper")) diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/AssetApiServiceTest.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/AssetApiServiceTest.java index b7bac21dc..d568ea527 100644 --- a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/AssetApiServiceTest.java +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/AssetApiServiceTest.java @@ -14,8 +14,11 @@ package de.sovity.edc.client; -import de.sovity.edc.client.gen.model.AssetCreateRequest; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; +import de.sovity.edc.client.gen.model.UiAsset; +import de.sovity.edc.client.gen.model.UiAssetCreateRequest; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.FailedMappingException; +import de.sovity.edc.utils.jsonld.vocab.Prop; import lombok.SneakyThrows; import org.eclipse.edc.connector.spi.asset.AssetService; import org.eclipse.edc.junit.annotations.ApiTest; @@ -28,17 +31,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import java.text.SimpleDateFormat; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.spi.types.domain.DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY; @ApiTest @ExtendWith(EdcExtension.class) public class AssetApiServiceTest { public static final String DATA_SINK = "http://my-data-sink/api/stuff"; - public static final String DATA_ADDRESS_TYPE = "HttpData"; EdcPropertyUtils edcPropertyUtils; @BeforeEach @@ -51,12 +53,11 @@ void setUp(EdcExtension extension) { void assetPage(AssetService assetStore) { // arrange var client = TestUtils.edcClient(); - var privateProperties = Map.of("random-private-prop", "456"); var properties = Map.of( Asset.PROPERTY_ID, "asset-1", - "random-prop", "123" + Prop.Dcat.LANDING_PAGE, "https://data-source.my-org/docs" ); - createAsset(assetStore, "2023-06-01", properties, privateProperties); + createAsset(assetStore, "2023-06-01", properties); // act var result = client.uiApi().assetPage(); @@ -65,25 +66,24 @@ void assetPage(AssetService assetStore) { var assets = result.getAssets(); assertThat(assets).hasSize(1); var asset = assets.get(0); - assertThat(asset.getProperties()).isEqualTo(properties); - assertThat(asset.getPrivateProperties()).isEqualTo(privateProperties); + assertThat(asset.getAssetId()).isEqualTo(properties.get(Asset.PROPERTY_ID)); + assertThat(asset.getLandingPageUrl()).isEqualTo(properties.get(Prop.Dcat.LANDING_PAGE)); } - @Test void assetPageSorting(AssetService assetService) { // arrange var client = TestUtils.edcClient(); - createAsset(assetService, "2023-06-01", Map.of(Asset.PROPERTY_ID, "asset-1"), Map.of()); - createAsset(assetService, "2023-06-03", Map.of(Asset.PROPERTY_ID, "asset-3"), Map.of()); - createAsset(assetService, "2023-06-02", Map.of(Asset.PROPERTY_ID, "asset-2"), Map.of()); + createAsset(assetService, "2023-06-01", Map.of(Asset.PROPERTY_ID, "asset-1")); + createAsset(assetService, "2023-06-03", Map.of(Asset.PROPERTY_ID, "asset-3")); + createAsset(assetService, "2023-06-02", Map.of(Asset.PROPERTY_ID, "asset-2")); // act var result = client.uiApi().assetPage(); // assert assertThat(result.getAssets()) - .extracting(asset -> asset.getProperties().get(Asset.PROPERTY_ID)) + .extracting(UiAsset::getAssetId) .containsExactly("asset-3", "asset-2", "asset-1"); } @@ -91,39 +91,124 @@ void assetPageSorting(AssetService assetService) { void testAssetCreation(AssetService assetService) { // arrange var client = TestUtils.edcClient(); - var properties = Map.of( - Asset.PROPERTY_ID, "asset-1", - "random-prop", "123" + var dataAddressProperties = Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.BASE_URL, DATA_SINK, + Prop.Edc.PROXY_METHOD, "true", + Prop.Edc.PROXY_PATH, "true", + Prop.Edc.PROXY_QUERY_PARAMS, "true", + Prop.Edc.PROXY_BODY, "true" + ); + var uiAssetRequest = UiAssetCreateRequest.builder() + .id("asset-1") + .name("AssetName") + .description("AssetDescription") + .licenseUrl("https://license-url") + .version("1.0.0") + .language("en") + .mediaType("application/json") + .dataCategory("dataCategory") + .dataSubcategory("dataSubcategory") + .dataModel("dataModel") + .geoReferenceMethod("geoReferenceMethod") + .transportMode("transportMode") + .keywords(List.of("keyword1", "keyword2")) + .creatorOrganizationName("creatorOrganizationName") + .publisherHomepage("publisherHomepage") + .dataAddressProperties(dataAddressProperties) + .build(); + + // act + var response = client.uiApi().createAsset(uiAssetRequest); + + // assert + assertThat(response.getId()).isEqualTo("asset-1"); + + var assets = client.uiApi().assetPage().getAssets(); + assertThat(assets).hasSize(1); + var asset = assets.get(0); + assertThat(asset.getAssetId()).isEqualTo("asset-1"); + assertThat(asset.getName()).isEqualTo("AssetName"); + assertThat(asset.getDescription()).isEqualTo("AssetDescription"); + assertThat(asset.getVersion()).isEqualTo("1.0.0"); + assertThat(asset.getLanguage()).isEqualTo("en"); + assertThat(asset.getMediaType()).isEqualTo("application/json"); + assertThat(asset.getDataCategory()).isEqualTo("dataCategory"); + assertThat(asset.getDataSubcategory()).isEqualTo("dataSubcategory"); + assertThat(asset.getDataModel()).isEqualTo("dataModel"); + assertThat(asset.getGeoReferenceMethod()).isEqualTo("geoReferenceMethod"); + assertThat(asset.getTransportMode()).isEqualTo("transportMode"); + assertThat(asset.getLicenseUrl()).isEqualTo("https://license-url"); + assertThat(asset.getKeywords()).isEqualTo(List.of("keyword1", "keyword2")); + assertThat(asset.getCreatorOrganizationName()).isEqualTo("creatorOrganizationName"); + assertThat(asset.getPublisherHomepage()).isEqualTo("publisherHomepage"); + assertThat(asset.getHttpDatasourceHintsProxyMethod()).isTrue(); + assertThat(asset.getHttpDatasourceHintsProxyPath()).isTrue(); + assertThat(asset.getHttpDatasourceHintsProxyQueryParams()).isTrue(); + assertThat(asset.getHttpDatasourceHintsProxyBody()).isTrue(); + + var assetWithDataAddress = assetService.query(QuerySpec.max()).orElseThrow(FailedMappingException::ofFailure).toList().get(0); + assertThat(assetWithDataAddress.getDataAddress().getProperties()).isEqualTo(dataAddressProperties); + } + + @Test + void testAssetCreation_noProxying() { + // arrange + var client = TestUtils.edcClient(); + var dataAddressProperties = Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.BASE_URL, DATA_SINK ); - var privateProperties = Map.of("random-private-prop", "456"); + var uiAssetRequest = UiAssetCreateRequest.builder() + .id("asset-1") + .dataAddressProperties(dataAddressProperties) + .build(); + + // act + var response = client.uiApi().createAsset(uiAssetRequest); + + // assert + assertThat(response.getId()).isEqualTo("asset-1"); + var assets = client.uiApi().assetPage().getAssets(); + assertThat(assets).hasSize(1); + var asset = assets.get(0); + assertThat(asset.getHttpDatasourceHintsProxyMethod()).isFalse(); + assertThat(asset.getHttpDatasourceHintsProxyPath()).isFalse(); + assertThat(asset.getHttpDatasourceHintsProxyQueryParams()).isFalse(); + assertThat(asset.getHttpDatasourceHintsProxyBody()).isFalse(); + } + + @Test + void testAssetCreation_differentDataAddressType() { + // arrange + var client = TestUtils.edcClient(); var dataAddressProperties = Map.of( - EDC_DATA_ADDRESS_TYPE_PROPERTY, DATA_ADDRESS_TYPE, - "baseUrl", DATA_SINK + Prop.Edc.TYPE, "Unknown" ); - var assetRequest = AssetCreateRequest.builder() - .properties(properties) - .privateProperties(privateProperties) + var uiAssetRequest = UiAssetCreateRequest.builder() + .id("asset-1") .dataAddressProperties(dataAddressProperties) .build(); // act - var response = client.uiApi().createAsset(assetRequest); + var response = client.uiApi().createAsset(uiAssetRequest); // assert - assertThat(response.getId()).isEqualTo(properties.get(Asset.PROPERTY_ID)); - var assets = assetService.query(QuerySpec.max()).getContent().toList(); + assertThat(response.getId()).isEqualTo("asset-1"); + var assets = client.uiApi().assetPage().getAssets(); assertThat(assets).hasSize(1); var asset = assets.get(0); - assertThat(asset.getProperties()).isEqualTo(properties); - assertThat(asset.getPrivateProperties()).isEqualTo(privateProperties); - assertThat(asset.getDataAddress().getProperties()).isEqualTo(dataAddressProperties); + assertThat(asset.getHttpDatasourceHintsProxyMethod()).isNull(); + assertThat(asset.getHttpDatasourceHintsProxyPath()).isNull(); + assertThat(asset.getHttpDatasourceHintsProxyQueryParams()).isNull(); + assertThat(asset.getHttpDatasourceHintsProxyBody()).isNull(); } @Test void testDeleteAsset(AssetService assetService) { // arrange var client = TestUtils.edcClient(); - createAsset(assetService, "2023-06-01", Map.of(Asset.PROPERTY_ID, "asset-1"), Map.of()); + createAsset(assetService, "2023-06-01", Map.of(Asset.PROPERTY_ID, "asset-1")); assertThat(assetService.query(QuerySpec.max()).getContent()).isNotEmpty(); // act @@ -137,20 +222,17 @@ void testDeleteAsset(AssetService assetService) { private void createAsset( AssetService assetService, String date, - Map properties, - Map privateProperties + Map properties ) { - DataAddress dataAddress = DataAddress.Builder.newInstance() - .type(DATA_ADDRESS_TYPE) - .property("baseUrl", DATA_SINK) + .type("HttpData") + .property(Prop.Edc.BASE_URL, DATA_SINK) .build(); var asset = Asset.Builder.newInstance() .id(properties.get(Asset.PROPERTY_ID)) .properties(edcPropertyUtils.toMapOfObject(properties)) .dataAddress(dataAddress) - .privateProperties(edcPropertyUtils.toMapOfObject(privateProperties)) .createdAt(dateFormatterToLong(date)) .build(); assetService.create(asset); @@ -161,6 +243,5 @@ private static long dateFormatterToLong(String date) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); return formatter.parse(date).getTime(); } - } diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractAgreementPageTest.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractAgreementPageTest.java index bb1376c89..6dac1f493 100644 --- a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractAgreementPageTest.java +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractAgreementPageTest.java @@ -17,6 +17,7 @@ import de.sovity.edc.client.gen.model.ContractAgreementCard; import de.sovity.edc.client.gen.model.TransferProcessState; import de.sovity.edc.client.gen.model.UiPolicyConstraint; +import de.sovity.edc.utils.jsonld.vocab.Prop; import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; @@ -93,8 +94,8 @@ void testContractAgreementPage( assertThat(agreement.getCounterPartyAddress()).isEqualTo("http://other-connector"); assertThat(agreement.getCounterPartyId()).isEqualTo("urn:connector:other-connector"); assertThat(agreement.getContractSigningDate()).isEqualTo(todayPlusDays(0)); - assertThat(agreement.getAsset().getProperties()).containsEntry("https://w3id.org/edc/v0.0.1/ns/id", ASSET_ID); - assertThat(agreement.getAsset().getProperties()).containsEntry("some-property", "X"); + assertThat(agreement.getAsset().getAssetId()).isEqualTo(ASSET_ID); + assertThat(agreement.getAsset().getLandingPageUrl()).isEqualTo("X"); assertThat(agreement.getTransferProcesses()).hasSize(1); var transfer = agreement.getTransferProcesses().get(0); @@ -178,7 +179,7 @@ private ContractNegotiation contractDefinition(int contract) { private Asset asset(String assetId) { return Asset.Builder.newInstance() .id(assetId) - .property("some-property", "X") + .property(Prop.Dcat.LANDING_PAGE, "X") .createdAt(todayEpochMillis) .dataAddress(dataAddress()) .build(); diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractDefinitionPageApiServiceTest.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractDefinitionPageApiServiceTest.java index 22f9108e0..1a120798e 100644 --- a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractDefinitionPageApiServiceTest.java +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractDefinitionPageApiServiceTest.java @@ -2,10 +2,8 @@ import de.sovity.edc.client.gen.model.ContractDefinitionEntry; import de.sovity.edc.client.gen.model.ContractDefinitionRequest; -import de.sovity.edc.client.gen.model.UiCriterionDto; -import de.sovity.edc.client.gen.model.UiCriterionLiteralDto; -import de.sovity.edc.ext.wrapper.api.common.mappers.OperatorMapper; -import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.CriterionMapper; +import de.sovity.edc.client.gen.model.UiCriterion; +import de.sovity.edc.client.gen.model.UiCriterionLiteral; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.spi.contractdefinition.ContractDefinitionService; import org.eclipse.edc.junit.annotations.ApiTest; @@ -23,21 +21,17 @@ @ApiTest @ExtendWith(EdcExtension.class) class ContractDefinitionPageApiServiceTest { - OperatorMapper operatorMapper; - CriterionMapper criterionMapper; @BeforeEach void setUp(EdcExtension extension) { TestUtils.setupExtension(extension); - operatorMapper = new OperatorMapper(); - criterionMapper = new CriterionMapper(operatorMapper); } @Test void contractDefinitionPage(ContractDefinitionService contractDefinitionService) { // arrange var client = TestUtils.edcClient(); - var criterion = new Criterion("exampleLeft1", "EQ", "abc"); + var criterion = new Criterion("exampleLeft1", "=", "abc"); createContractDefinition(contractDefinitionService, "contractDefinition-id-1", "contractPolicy-id-1", "accessPolicy-id-1", criterion); // act @@ -54,8 +48,8 @@ void contractDefinitionPage(ContractDefinitionService contractDefinitionService) var criterionEntry = contractDefinition.getAssetSelector().get(0); assertThat(criterionEntry.getOperandLeft()).isEqualTo("exampleLeft1"); - assertThat(criterionEntry.getOperator()).isEqualTo(UiCriterionDto.OperatorEnum.EQ); - assertThat(criterionEntry.getOperandRight().getType()).isEqualTo(UiCriterionLiteralDto.TypeEnum.VALUE); + assertThat(criterionEntry.getOperator()).isEqualTo(UiCriterion.OperatorEnum.EQ); + assertThat(criterionEntry.getOperandRight().getType()).isEqualTo(UiCriterionLiteral.TypeEnum.VALUE); assertThat(criterionEntry.getOperandRight().getValue()).isEqualTo("abc"); } @@ -68,21 +62,21 @@ void contractDefinitionPageSorting(ContractDefinitionService contractDefinitionS "contractDefinition-id-1", "contractPolicy-id-1", "accessPolicy-id-1", - new Criterion("exampleLeft1", "EQ", "abc"), + new Criterion("exampleLeft1", "=", "abc"), 1628956800000L); createContractDefinition( contractDefinitionService, "contractDefinition-id-2", "contractPolicy-id-2", "accessPolicy-id-2", - new Criterion("exampleLeft1", "EQ", "abc"), + new Criterion("exampleLeft1", "=", "abc"), 1628956801000L); createContractDefinition( contractDefinitionService, "contractDefinition-id-3", "contractPolicy-id-3", "accessPolicy-id-3", - new Criterion("exampleLeft1", "EQ", "abc"), + new Criterion("exampleLeft1", "=", "abc"), 1628956802000L); // act @@ -99,10 +93,10 @@ void contractDefinitionPageSorting(ContractDefinitionService contractDefinitionS void testContractDefinitionCreation(ContractDefinitionService contractDefinitionService) { // arrange var client = TestUtils.edcClient(); - var criterion = new UiCriterionDto( + var criterion = new UiCriterion( "exampleLeft1", - UiCriterionDto.OperatorEnum.EQ, - new UiCriterionLiteralDto(UiCriterionLiteralDto.TypeEnum.VALUE, "test", null)); + UiCriterion.OperatorEnum.EQ, + new UiCriterionLiteral(UiCriterionLiteral.TypeEnum.VALUE, "test", null)); var contractDefinition = ContractDefinitionRequest.builder() .contractDefinitionId("contractDefinition-id-1") @@ -125,8 +119,8 @@ void testContractDefinitionCreation(ContractDefinitionService contractDefinition var criterionEntry = contractDefinition.getAssetSelector().get(0); assertThat(criterionEntry.getOperandLeft()).isEqualTo("exampleLeft1"); - assertThat(criterionEntry.getOperator()).isEqualTo(UiCriterionDto.OperatorEnum.EQ); - assertThat(criterionEntry.getOperandRight().getType()).isEqualTo(UiCriterionLiteralDto.TypeEnum.VALUE); + assertThat(criterionEntry.getOperator()).isEqualTo(UiCriterion.OperatorEnum.EQ); + assertThat(criterionEntry.getOperandRight().getType()).isEqualTo(UiCriterionLiteral.TypeEnum.VALUE); assertThat(criterionEntry.getOperandRight().getValue()).isEqualTo("test"); } @@ -134,7 +128,7 @@ void testContractDefinitionCreation(ContractDefinitionService contractDefinition void testDeleteContractDefinition(ContractDefinitionService contractDefinitionService) { // arrange var client = TestUtils.edcClient(); - var criterion = new Criterion("exampleLeft1", "EQ", "exampleRight1"); + var criterion = new Criterion("exampleLeft1", "=", "exampleRight1"); createContractDefinition(contractDefinitionService, "contractDefinition-id-1", "contractPolicy-id-1", "accessPolicy-id-1", criterion); assertThat(contractDefinitionService.query(QuerySpec.max()).getContent().toList()).hasSize(1); var contractDefinition = contractDefinitionService.query(QuerySpec.max()).getContent().toList().get(0); diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/TestUtils.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/TestUtils.java index 0662c0f0a..0e10d23f1 100644 --- a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/TestUtils.java +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/TestUtils.java @@ -57,7 +57,6 @@ public static Map createConfiguration( public static void setupExtension(EdcExtension extension) { extension.registerServiceMock(ProtocolWebhook.class, mock(ProtocolWebhook.class)); - extension.registerServiceMock(JsonLd.class, mock(JsonLd.class)); extension.setConfiguration(createConfiguration(Map.of())); } diff --git a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java index b5d8f7c10..1b2194583 100644 --- a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java +++ b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAsset.java @@ -22,17 +22,91 @@ import lombok.Setter; import lombok.ToString; +import java.util.List; +import java.util.Map; + @Getter @Setter + @ToString @AllArgsConstructor @Builder(toBuilder = true) @RequiredArgsConstructor @Schema(description = "Type-Safe Asset Metadata as needed by our UI") public class UiAsset { - @Schema(description = "Asset ID", requiredMode = Schema.RequiredMode.REQUIRED) + + @Schema(description = "Asset Id", requiredMode = Schema.RequiredMode.REQUIRED) private String assetId; - @Schema(description = "Asset name") + @Schema(description = "Asset Name", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private String name; + + @Schema(description = "Asset Language", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String language; + + @Schema(description = "Asset Description", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String description; + + @Schema(description = "Asset Organization Name", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String creatorOrganizationName; + + @Schema(description = "Asset Homepage", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String publisherHomepage; + + @Schema(description = "License URL", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String licenseUrl; + + @Schema(description = "Version", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String version; + + @Schema(description = "Asset Keywords", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private List keywords; + + @Schema(description = "Asset MediaType", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String mediaType; + + @Schema(description = "Homepage URL associated with the Asset", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String landingPageUrl; + + @Schema(description = "HTTP Datasource Hints Proxy Method", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Boolean httpDatasourceHintsProxyMethod; + + @Schema(description = "HTTP Datasource Hints Proxy Path", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Boolean httpDatasourceHintsProxyPath; + + @Schema(description = "HTTP Datasource Hints Proxy Query Params", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Boolean httpDatasourceHintsProxyQueryParams; + + @Schema(description = "HTTP Datasource Hints Proxy Body", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Boolean httpDatasourceHintsProxyBody; + + @Schema(description = "Data Category", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataCategory; + + @Schema(description = "Data Subcategory", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataSubcategory; + + @Schema(description = "Data Model", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataModel; + + @Schema(description = "Geo-Reference Method", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String geoReferenceMethod; + + @Schema(description = "Transport Mode", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String transportMode; + + @Schema(description = "Unhandled Asset Properties (that were strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map additionalProperties; + + @Schema(description = "Unhandled Asset Properties (that were not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map additionalJsonProperties; + + @Schema(description = "Private Asset Properties (that were strings)", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map privateProperties; + + @Schema(description = "Private Asset Properties (that were not strings but other JSON values)", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map privateJsonProperties; + + @Schema(description = "Contains the entire asset in the JSON-LD format", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String assetJsonLd; } diff --git a/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java new file mode 100644 index 000000000..30f18aade --- /dev/null +++ b/extensions/wrapper/wrapper-common-api/src/main/java/de/sovity/edc/ext/wrapper/api/common/model/UiAssetCreateRequest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 sovity GmbH + * + * 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: + * sovity GmbH - initial API and implementation + * + */ + +package de.sovity.edc.ext.wrapper.api.common.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Type-Safe OpenAPI generator friendly Asset Create DTO that supports an opinionated subset of the original EDC Asset Entity.") +public class UiAssetCreateRequest { + @Schema(description = "Asset Id", requiredMode = Schema.RequiredMode.REQUIRED) + private String id; + + @Schema(description = "Asset Name", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String name; + + @Schema(description = "Asset Language", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String language; + + @Schema(description = "Asset Description", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String description; + + @Schema(description = "Asset Organization Name", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String creatorOrganizationName; + + @Schema(description = "Asset Homepage", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String publisherHomepage; + + @Schema(description = "License URL", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String licenseUrl; + + @Schema(description = "Version", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String version; + + @Schema(description = "Asset Keywords", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private List keywords; + + @Schema(description = "Asset MediaType", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String mediaType; + + @Schema(description = "Landing Page URL", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String landingPageUrl; + + @Schema(description = "Data Category", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataCategory; + + @Schema(description = "Data Subcategory", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataSubcategory; + + @Schema(description = "Data Model", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String dataModel; + + @Schema(description = "Geo-Reference Method", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String geoReferenceMethod; + + @Schema(description = "Transport Mode", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String transportMode; + + @Schema(description = "Data Address", requiredMode = Schema.RequiredMode.REQUIRED) + private Map dataAddressProperties; + + @Schema(description = "Asset additional Properties", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map additionalProperties; + + @Schema(description = "Asset Private Properties", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map privateProperties; + + @Schema(description = "Asset Json Properties", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private Map additionalJsonProperties; +} diff --git a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts index a3cd3113a..8f6de4f2f 100644 --- a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts +++ b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts @@ -15,18 +15,17 @@ dependencies { compileOnly("org.projectlombok:lombok:${lombokVersion}") api("${edcGroup}:policy-model:${edcVersion}") - api("${edcGroup}:transform-core:${edcVersion}") api("${edcGroup}:core-spi:${edcVersion}") + api("${edcGroup}:transform-core:${edcVersion}") api("${edcGroup}:transform-spi:${edcVersion}") api(project(":extensions:wrapper:wrapper-common-api")) - - implementation(project(":utils:json-and-jsonld-utils")) - + api(project(":utils:json-and-jsonld-utils")) implementation("org.apache.commons:commons-lang3:3.13.0") implementation("org.apache.commons:commons-collections4:4.4") testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}") testCompileOnly("org.projectlombok:lombok:${lombokVersion}") + testImplementation("${edcGroup}:json-ld:${edcVersion}") testImplementation("org.mockito:mockito-core:${mockitoVersion}") testImplementation("org.mockito:mockito-inline:${mockitoVersion}") testImplementation("org.mockito:mockito-junit-jupiter:${mockitoVersion}") diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapper.java index 63e1dfafe..c1727e2e3 100644 --- a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapper.java +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapper.java @@ -1,49 +1,66 @@ package de.sovity.edc.ext.wrapper.api.common.mappers; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.FailedMappingException; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.UiAssetMapper; import de.sovity.edc.ext.wrapper.api.common.model.UiAsset; -import de.sovity.edc.utils.JsonUtils; +import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; import de.sovity.edc.utils.jsonld.JsonLdUtils; import de.sovity.edc.utils.jsonld.vocab.Prop; import jakarta.json.Json; import jakarta.json.JsonObject; import lombok.RequiredArgsConstructor; +import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.spi.types.domain.asset.Asset; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import java.util.Optional; + @RequiredArgsConstructor public class AssetMapper { private final TypeTransformerRegistry typeTransformerRegistry; + private final UiAssetMapper uiAssetMapper; + private final JsonLd jsonLd; public UiAsset buildUiAsset(Asset asset) { - String json = buildJsonLd(asset); + var assetJsonLd = buildAssetJsonLd(asset); + return buildUiAsset(assetJsonLd); + } + public UiAsset buildUiAsset(JsonObject assetJsonLd) { + return uiAssetMapper.buildUiAsset(assetJsonLd); + } + + public Asset buildAsset(UiAssetCreateRequest createRequest) { + var assetJsonLd = uiAssetMapper.buildAssetJsonLd(createRequest); + return buildAsset(assetJsonLd); + } - // TODO add more fields - return new UiAsset( - asset.getId(), - asset.getName() - ); + public Asset buildAssetFromDatasetProperties(JsonObject json) { + return buildAsset(buildAssetJsonLdFromDatasetProperties(json)); } - public Asset buildAssetFromAssetPropertiesJsonLd(JsonObject json) { - var assetJsonLd = buildJsonLdFromProperties(json); - return typeTransformerRegistry.transform(assetJsonLd, Asset.class).getContent(); + public Asset buildAsset(JsonObject assetJsonLd) { + var expanded = jsonLd.expand(assetJsonLd) + .orElseThrow(FailedMappingException::ofFailure); + return typeTransformerRegistry.transform(expanded, Asset.class) + .orElseThrow(FailedMappingException::ofFailure); } - private String buildJsonLd(Asset asset) { - return JsonUtils.toJson(typeTransformerRegistry.transform(asset, JsonObject.class).getContent()); + private JsonObject buildAssetJsonLd(Asset asset) { + var assetJsonLd = typeTransformerRegistry.transform(asset, JsonObject.class) + .orElseThrow(FailedMappingException::ofFailure); + return jsonLd.expand(assetJsonLd) + .orElseThrow(FailedMappingException::ofFailure); } - private JsonObject buildJsonLdFromProperties(JsonObject json) { + private JsonObject buildAssetJsonLdFromDatasetProperties(JsonObject json) { // Try to use the EDC Prop ID, but if it's not available, fall back to the "@id" property var assetId = Optional.ofNullable(JsonLdUtils.string(json, Prop.Edc.ID)) .orElseGet(() -> JsonLdUtils.string(json, Prop.ID)); return Json.createObjectBuilder() .add(Prop.ID, assetId) - .add(Prop.TYPE, Prop.Edc.TYPE_ASSET) .add(Prop.Edc.PROPERTIES, json) .build(); } diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java index 778bf9f46..be9a8bbd4 100644 --- a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java @@ -2,6 +2,7 @@ import de.sovity.edc.ext.wrapper.api.common.mappers.utils.AtomicConstraintMapper; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.ConstraintExtractor; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.FailedMappingException; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.MappingErrors; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.PolicyValidator; import de.sovity.edc.ext.wrapper.api.common.model.UiPolicyCreateRequest; @@ -80,7 +81,8 @@ public Policy buildPolicy(UiPolicyCreateRequest policyCreateDto) { * @return {@link Policy} */ public Policy buildPolicy(JsonObject policyJsonLd) { - return typeTransformerRegistry.transform(policyJsonLd, Policy.class).getContent(); + return typeTransformerRegistry.transform(policyJsonLd, Policy.class) + .orElseThrow(FailedMappingException::ofFailure); } /** @@ -104,6 +106,7 @@ public Policy buildPolicy(String policyJsonLd) { * @return policy JSON-LD */ public JsonObject buildPolicyJsonLd(Policy policy) { - return typeTransformerRegistry.transform(policy, JsonObject.class).getContent(); + return typeTransformerRegistry.transform(policy, JsonObject.class) + .orElseThrow(FailedMappingException::ofFailure); } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtils.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtils.java similarity index 93% rename from extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtils.java rename to extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtils.java index ca1d36370..5af531863 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtils.java +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtils.java @@ -11,7 +11,7 @@ * sovity GmbH - init */ -package de.sovity.edc.ext.wrapper.utils; +package de.sovity.edc.ext.wrapper.api.common.mappers.utils; import lombok.RequiredArgsConstructor; import org.eclipse.edc.spi.types.domain.DataAddress; @@ -34,6 +34,10 @@ public class EdcPropertyUtils { public Map truncateToMapOfString(Map map) { Map result = new HashMap<>(); + if (map == null) { + return result; + } + for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); @@ -58,7 +62,7 @@ public Map truncateToMapOfString(Map map) { @SuppressWarnings({"unchecked", "rawtypes", "java:S1905"}) public Map toMapOfObject(Map map) { if (map == null) { - return new HashMap<>(); + return Map.of(); } return new HashMap<>((Map) (Map) map); } diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/FailedMappingException.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/FailedMappingException.java new file mode 100644 index 000000000..d9b5ea056 --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/FailedMappingException.java @@ -0,0 +1,13 @@ +package de.sovity.edc.ext.wrapper.api.common.mappers.utils; + +import org.eclipse.edc.spi.result.Failure; + +public class FailedMappingException extends RuntimeException { + public FailedMappingException(String message) { + super(message); + } + + public static FailedMappingException ofFailure(Failure failure) { + return new FailedMappingException(failure.getFailureDetail()); + } +} diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java new file mode 100644 index 000000000..8e8d7e975 --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/JsonBuilderUtils.java @@ -0,0 +1,27 @@ +package de.sovity.edc.ext.wrapper.api.common.mappers.utils; + +import jakarta.json.Json; +import jakarta.json.JsonObjectBuilder; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonBuilderUtils { + + protected static JsonObjectBuilder addNonNull(JsonObjectBuilder builder, String key, String value) { + if (value != null) { + builder.add(key, value); + } + return builder; + } + + protected static JsonObjectBuilder addNonNullArray(JsonObjectBuilder builder, String key, List values) { + if (CollectionUtils.isNotEmpty(values)) { + builder.add(key, Json.createArrayBuilder(values)); + } + return builder; + } +} diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java new file mode 100644 index 000000000..4ed2139b8 --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/UiAssetMapper.java @@ -0,0 +1,130 @@ +package de.sovity.edc.ext.wrapper.api.common.mappers.utils; + +import de.sovity.edc.ext.wrapper.api.common.model.UiAsset; +import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; +import de.sovity.edc.utils.JsonUtils; +import de.sovity.edc.utils.jsonld.JsonLdUtils; +import de.sovity.edc.utils.jsonld.vocab.Prop; +import de.sovity.edc.utils.jsonld.vocab.Prop.SovityDcatExt.HttpDatasourceHints; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +import static de.sovity.edc.ext.wrapper.api.common.mappers.utils.JsonBuilderUtils.addNonNull; +import static de.sovity.edc.ext.wrapper.api.common.mappers.utils.JsonBuilderUtils.addNonNullArray; + +@RequiredArgsConstructor +public class UiAssetMapper { + private final EdcPropertyUtils edcPropertyUtils; + private final JsonLd jsonLd; + + public UiAsset buildUiAsset(JsonObject assetJsonLd) { + var properties = JsonLdUtils.object(assetJsonLd, Prop.Edc.PROPERTIES); + + var uiAsset = new UiAsset(); + uiAsset.setAssetJsonLd(buildCompactAssetJsonLd(assetJsonLd)); + + uiAsset.setAssetId(JsonLdUtils.string(assetJsonLd, Prop.ID)); + uiAsset.setLicenseUrl(JsonLdUtils.string(properties, Prop.Dcterms.LICENSE)); + uiAsset.setName(JsonLdUtils.string(properties, Prop.Dcterms.NAME)); + uiAsset.setDescription(JsonLdUtils.string(properties, Prop.Dcterms.DESCRIPTION)); + uiAsset.setLanguage(JsonLdUtils.string(properties, Prop.Dcterms.LANGUAGE)); + uiAsset.setVersion(JsonLdUtils.string(properties, Prop.Dcat.VERSION)); + uiAsset.setMediaType(JsonLdUtils.string(properties, Prop.Dcat.MEDIATYPE)); + uiAsset.setLandingPageUrl(JsonLdUtils.string(properties, Prop.Dcat.LANDING_PAGE)); + uiAsset.setDataCategory(JsonLdUtils.string(properties, Prop.Mds.DATA_CATEGORY)); + uiAsset.setDataSubcategory(JsonLdUtils.string(properties, Prop.Mds.DATA_SUBCATEGORY)); + uiAsset.setDataModel(JsonLdUtils.string(properties, Prop.Mds.DATA_MODEL)); + uiAsset.setGeoReferenceMethod(JsonLdUtils.string(properties, Prop.Mds.GEO_REFERENCE_METHOD)); + uiAsset.setTransportMode(JsonLdUtils.string(properties, Prop.Mds.TRANSPORT_MODE)); + uiAsset.setKeywords(JsonLdUtils.stringList(properties, Prop.Dcat.KEYWORDS)); + + uiAsset.setHttpDatasourceHintsProxyMethod(JsonLdUtils.bool(properties, HttpDatasourceHints.METHOD)); + uiAsset.setHttpDatasourceHintsProxyPath(JsonLdUtils.bool(properties, HttpDatasourceHints.PATH)); + uiAsset.setHttpDatasourceHintsProxyQueryParams(JsonLdUtils.bool(properties, HttpDatasourceHints.QUERY_PARAMS)); + uiAsset.setHttpDatasourceHintsProxyBody(JsonLdUtils.bool(properties, HttpDatasourceHints.BODY)); + + var publisher = JsonLdUtils.object(properties, Prop.Dcterms.PUBLISHER); + uiAsset.setPublisherHomepage(JsonLdUtils.string(publisher, Prop.Foaf.HOMEPAGE)); + + var creator = JsonLdUtils.object(properties, Prop.Dcterms.CREATOR); + uiAsset.setCreatorOrganizationName(JsonLdUtils.string(creator, Prop.Foaf.NAME)); + + return uiAsset; + } + + private String buildCompactAssetJsonLd(JsonObject assetJsonLd) { + var compacted = jsonLd.compact(assetJsonLd).orElseThrow(FailedMappingException::ofFailure); + return JsonUtils.toJson(compacted); + } + + @SneakyThrows + @Nullable + public JsonObject buildAssetJsonLd(UiAssetCreateRequest uiAssetCreateRequest) { + var properties = getAssetProperties(uiAssetCreateRequest); + var dataAddress = getDataAddress(uiAssetCreateRequest); + + return Json.createObjectBuilder() + .add(Prop.ID, uiAssetCreateRequest.getId()) + .add(Prop.TYPE, Prop.Edc.TYPE_ASSET) + .add(Prop.Edc.PROPERTIES, properties) + .add(Prop.Edc.DATA_ADDRESS, dataAddress) + .build(); + } + + private JsonObjectBuilder getAssetProperties(UiAssetCreateRequest uiAssetCreateRequest) { + var properties = Json.createObjectBuilder(); + + addNonNull(properties, Prop.Edc.ID, uiAssetCreateRequest.getId()); + addNonNull(properties, Prop.Dcterms.LICENSE, uiAssetCreateRequest.getLicenseUrl()); + addNonNull(properties, Prop.Dcterms.NAME, uiAssetCreateRequest.getName()); + addNonNull(properties, Prop.Dcterms.DESCRIPTION, uiAssetCreateRequest.getDescription()); + addNonNull(properties, Prop.Dcterms.LANGUAGE, uiAssetCreateRequest.getLanguage()); + addNonNull(properties, Prop.Dcat.VERSION, uiAssetCreateRequest.getVersion()); + addNonNull(properties, Prop.Dcat.MEDIATYPE, uiAssetCreateRequest.getMediaType()); + addNonNull(properties, Prop.Dcat.LANDING_PAGE, uiAssetCreateRequest.getLandingPageUrl()); + addNonNull(properties, Prop.Mds.DATA_CATEGORY, uiAssetCreateRequest.getDataCategory()); + addNonNull(properties, Prop.Mds.DATA_SUBCATEGORY, uiAssetCreateRequest.getDataSubcategory()); + addNonNull(properties, Prop.Mds.DATA_MODEL, uiAssetCreateRequest.getDataModel()); + addNonNull(properties, Prop.Mds.GEO_REFERENCE_METHOD, uiAssetCreateRequest.getGeoReferenceMethod()); + addNonNull(properties, Prop.Mds.TRANSPORT_MODE, uiAssetCreateRequest.getTransportMode()); + addNonNullArray(properties, Prop.Dcat.KEYWORDS, uiAssetCreateRequest.getKeywords()); + + if (uiAssetCreateRequest.getPublisherHomepage() != null) { + properties.add(Prop.Dcterms.PUBLISHER, Json.createObjectBuilder() + .add(Prop.Foaf.HOMEPAGE, uiAssetCreateRequest.getPublisherHomepage())); + } + + if (uiAssetCreateRequest.getCreatorOrganizationName() != null) { + properties.add(Prop.Dcterms.CREATOR, Json.createObjectBuilder() + .add(Prop.Foaf.NAME, uiAssetCreateRequest.getCreatorOrganizationName())); + } + + var dataAddress = uiAssetCreateRequest.getDataAddressProperties(); + if (dataAddress.get(Prop.Edc.TYPE).equals("HttpData")) { + addNonNull(properties, HttpDatasourceHints.BODY, trueIfTrue(dataAddress, Prop.Edc.PROXY_BODY)); + addNonNull(properties, HttpDatasourceHints.PATH, trueIfTrue(dataAddress, Prop.Edc.PROXY_PATH)); + addNonNull(properties, HttpDatasourceHints.QUERY_PARAMS, trueIfTrue(dataAddress, Prop.Edc.PROXY_QUERY_PARAMS)); + addNonNull(properties, HttpDatasourceHints.METHOD, trueIfTrue(dataAddress, Prop.Edc.PROXY_METHOD)); + } + + return properties; + } + + private String trueIfTrue(Map dataAddressProperties, String key) { + return "true".equals(dataAddressProperties.get(key)) ? "true" : "false"; + } + + private JsonObjectBuilder getDataAddress(UiAssetCreateRequest uiAssetCreateRequest) { + var props = edcPropertyUtils.toMapOfObject(uiAssetCreateRequest.getDataAddressProperties()); + return Json.createObjectBuilder() + .add(Prop.TYPE, Prop.Edc.TYPE_DATA_ADDRESS) + .add(Prop.Edc.PROPERTIES, Json.createObjectBuilder(props)); + } +} diff --git a/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java new file mode 100644 index 000000000..a3e97226f --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/AssetMapperTest.java @@ -0,0 +1,170 @@ +package de.sovity.edc.ext.wrapper.api.common.mappers; + +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.UiAssetMapper; +import de.sovity.edc.utils.JsonUtils; +import de.sovity.edc.utils.jsonld.vocab.Prop; +import lombok.SneakyThrows; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class AssetMapperTest { + AssetMapper assetMapper; + + @BeforeEach + void setup() { + var jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + var typeTransformerRegistry = mock(TypeTransformerRegistry.class); + var uiAssetBuilder = new UiAssetMapper(new EdcPropertyUtils(), jsonLd); + assetMapper = new AssetMapper(typeTransformerRegistry, uiAssetBuilder, jsonLd); + } + + @Test + @SneakyThrows + void test_buildAssetDto() { + // Arrange + String assetJsonLd = new String(Files.readAllBytes(Paths.get(getClass().getResource("/example-asset-jsonld.json").toURI()))); + + // Act + var uiAsset = assetMapper.buildUiAsset(JsonUtils.parseJsonObj(assetJsonLd)); + + // Assert + assertThat(uiAsset.getAssetId()).isEqualTo("urn:artifact:my-asset"); + assertThat(uiAsset.getName()).isEqualTo("My Asset"); + assertThat(uiAsset.getLanguage()).isEqualTo("https://w3id.org/idsa/code/EN"); + assertThat(uiAsset.getDescription()).isEqualTo("Lorem Ipsum ..."); + assertThat(uiAsset.getCreatorOrganizationName()).isEqualTo("My Organization Name"); + assertThat(uiAsset.getPublisherHomepage()).isEqualTo("https://data-source.my-org/about"); + assertThat(uiAsset.getLicenseUrl()).isEqualTo("https://data-source.my-org/license"); + assertThat(uiAsset.getVersion()).isEqualTo("1.1"); + assertThat(uiAsset.getKeywords()).isEqualTo(List.of("some", "keywords")); + assertThat(uiAsset.getMediaType()).isEqualTo("application/json"); + assertThat(uiAsset.getLandingPageUrl()).isEqualTo("https://data-source.my-org/docs"); + assertThat(uiAsset.getHttpDatasourceHintsProxyMethod()).isTrue(); + assertThat(uiAsset.getHttpDatasourceHintsProxyPath()).isTrue(); + assertThat(uiAsset.getHttpDatasourceHintsProxyQueryParams()).isTrue(); + assertThat(uiAsset.getHttpDatasourceHintsProxyBody()).isTrue(); + assertThat(uiAsset.getDataCategory()).isEqualTo("Infrastructure and Logistics"); + assertThat(uiAsset.getDataSubcategory()).isEqualTo("General Information About Planning Of Routes"); + assertThat(uiAsset.getDataModel()).isEqualTo("my-data-model-001"); + assertThat(uiAsset.getGeoReferenceMethod()).isEqualTo("my-geo-reference-method"); + assertThat(uiAsset.getTransportMode()).isEqualTo("my-geo-reference-method"); + assertThat(uiAsset.getAssetJsonLd()).contains("\"%s\"".formatted(Prop.Edc.ID)); + } + + @Test + @SneakyThrows + void test_KeywordsAsSingleString() { + + // Arrange + var assetJsonLd = createObjectBuilder() + .add(Prop.ID, "my-asset-1") + .add(Prop.Edc.PROPERTIES, createObjectBuilder() + .add(Prop.Dcat.KEYWORDS, "SingleElement") + .build()) + .build(); + // Act + var uiAsset = assetMapper.buildUiAsset(assetJsonLd); + + // Assert + assertThat(uiAsset).isNotNull(); + assertThat(uiAsset.getKeywords()).isEqualTo(List.of("SingleElement")); + } + + @Test + @SneakyThrows + void test_StringValueWrappedInAtValue() { + + // Arrange + var assetJsonLd = createObjectBuilder() + .add(Prop.ID, "my-asset-1") + .add(Prop.Edc.PROPERTIES, createObjectBuilder() + .add(Prop.Dcterms.NAME, createObjectBuilder() + .add(Prop.VALUE, "AssetName") + .add(Prop.LANGUAGE, "en"))) + .build(); + + // Act + var uiAsset = assetMapper.buildUiAsset(assetJsonLd); + + // Assert + assertThat(uiAsset).isNotNull(); + assertThat(uiAsset.getName()).isEqualTo("AssetName"); + } + + @Test + @SneakyThrows + void test_StringsAsMap() { + + // Arrange + var properties = createObjectBuilder() + .add(Prop.Dcterms.NAME, createArrayBuilder() + .add(createObjectBuilder() + .add(Prop.TYPE, "SomeType") + .add(Prop.VALUE, "AssetName") + ) + ) + .build(); + var assetJsonLd = createObjectBuilder() + .add(Prop.ID, "my-asset-1") + .add(Prop.Edc.PROPERTIES, properties) + .build(); + + // Act + var uiAsset = assetMapper.buildUiAsset(assetJsonLd); + + // Assert + assertThat(uiAsset).isNotNull(); + assertThat(uiAsset.getName()).isEqualTo("AssetName"); + } + + @Test + @SneakyThrows + void test_badBooleanValue() { + // Arrange + var assetJsonLd = createObjectBuilder() + .add(Prop.ID, "my-asset-1") + .add(Prop.Edc.PROPERTIES, createObjectBuilder() + .add(Prop.SovityDcatExt.HttpDatasourceHints.METHOD, "wrongBooleanValue") + .build()) + .build(); + + // Act + var uiAsset = assetMapper.buildUiAsset(assetJsonLd); + + // Assert + assertThat(uiAsset).isNotNull(); + assertThat(uiAsset.getHttpDatasourceHintsProxyMethod()).isNull(); + } + + @Test + @SneakyThrows + void test_noBooleanValue() { + // Arrange + var assetJsonLd = createObjectBuilder() + .add(Prop.ID, "my-asset-1") + .add(Prop.Edc.PROPERTIES, createObjectBuilder() + .add(Prop.SovityDcatExt.HttpDatasourceHints.METHOD, "") + .build()) + .build(); + + // Act + var uiAsset = assetMapper.buildUiAsset(assetJsonLd); + + // Assert + assertThat(uiAsset).isNotNull(); + assertThat(uiAsset.getHttpDatasourceHintsProxyMethod()).isNull(); + } +} diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtilsTest.java b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtilsTest.java similarity index 96% rename from extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtilsTest.java rename to extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtilsTest.java index 592dbe30b..d95246d45 100644 --- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/EdcPropertyUtilsTest.java +++ b/extensions/wrapper/wrapper-common-mappers/src/test/java/de/sovity/edc/ext/wrapper/api/common/mappers/utils/EdcPropertyUtilsTest.java @@ -12,7 +12,7 @@ * */ -package de.sovity.edc.ext.wrapper.utils; +package de.sovity.edc.ext.wrapper.api.common.mappers.utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset-jsonld.json b/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset-jsonld.json new file mode 100644 index 000000000..f67794d8d --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/test/resources/example-asset-jsonld.json @@ -0,0 +1,44 @@ +{ + "@id": "urn:artifact:my-asset", + "@type": "https://w3id.org/edc/v0.0.1/ns/Asset", + "https://w3id.org/edc/v0.0.1/ns/properties": { + "https://w3id.org/edc/v0.0.1/ns/id": "urn:artifact:my-asset", + "http://purl.org/dc/terms/identifier": "urn:artifact:my-asset", + "http://purl.org/dc/terms/title": "My Asset", + "http://purl.org/dc/terms/language": "https://w3id.org/idsa/code/EN", + "http://purl.org/dc/terms/description": "Lorem Ipsum ...", + "http://purl.org/dc/terms/creator": { + "@type": "http://xmlns.com/foaf/0.1/Organization", + "http://xmlns.com/foaf/0.1/name": "My Organization Name" + }, + "http://purl.org/dc/terms/publisher": { + "@type": "http://xmlns.com/foaf/0.1/Organization", + "http://xmlns.com/foaf/0.1/homepage": "https://data-source.my-org/about" + }, + "http://purl.org/dc/terms/license": "https://data-source.my-org/license", + "http://www.w3.org/ns/dcat#version": "1.1", + "http://www.w3.org/ns/dcat#keyword": [ + "some", + "keywords" + ], + "http://www.w3.org/ns/dcat#mediaType": "application/json", + "http://www.w3.org/ns/dcat#landingPage": "https://data-source.my-org/docs", + "https://semantic.sovity.io/dcat-ext#httpDatasourceHintsProxyMethod": "true", + "https://semantic.sovity.io/dcat-ext#httpDatasourceHintsProxyPath": "true", + "https://semantic.sovity.io/dcat-ext#httpDatasourceHintsProxyQueryParams": "true", + "https://semantic.sovity.io/dcat-ext#httpDatasourceHintsProxyBody": "true", + "http://w3id.org/mds#dataCategory": "Infrastructure and Logistics", + "http://w3id.org/mds#dataSubcategory": "General Information About Planning Of Routes", + "http://w3id.org/mds#dataModel": "my-data-model-001", + "http://w3id.org/mds#geoReferenceMethod": "my-geo-reference-method", + "http://w3id.org/mds#transportMode": "my-geo-reference-method", + "http://unknown/some-custom-property": "test" + }, + "privateProperties": { + "idk": "abc" + }, + "https://w3id.org/edc/v0.0.1/ns/DataAddress": { + "https://w3id.org/edc/v0.0.1/ns/type": "HttpData", + "https://w3id.org/edc/v0.0.1/ns/baseUrl": "https://data-source.my-org" + } +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java index 2410e804c..1b932fe79 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java @@ -94,7 +94,6 @@ public void initialize(ServiceExtensionContext context) { var objectMapper = typeManager.getMapper(CoreConstants.JSON_LD); fixObjectMapperDateSerialization(objectMapper); - var wrapperExtensionContext = WrapperExtensionContextBuilder.buildContext( assetIndex, assetService, diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java index d93e76f1f..d58598873 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java @@ -20,15 +20,18 @@ import de.sovity.edc.ext.wrapper.api.common.mappers.PolicyMapper; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.AtomicConstraintMapper; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.ConstraintExtractor; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.LiteralMapper; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.PolicyValidator; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.UiAssetMapper; import de.sovity.edc.ext.wrapper.api.ui.UiResource; import de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetApiService; -import de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetBuilder; import de.sovity.edc.ext.wrapper.api.ui.pages.catalog.CatalogApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.ContractDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.ContractDefinitionBuilder; +import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.CriterionLiteralMapper; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.CriterionMapper; +import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.CriterionOperatorMapper; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_negotiations.ContractNegotiationApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_negotiations.ContractNegotiationBuilder; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_negotiations.ContractNegotiationStateService; @@ -49,7 +52,6 @@ import de.sovity.edc.ext.wrapper.api.usecase.services.OfferingService; import de.sovity.edc.ext.wrapper.api.usecase.services.PolicyMappingService; import de.sovity.edc.ext.wrapper.api.usecase.services.SupportedPolicyApiService; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; import de.sovity.edc.utils.catalog.DspCatalogService; import de.sovity.edc.utils.catalog.mapper.DspDataOfferBuilder; import lombok.NoArgsConstructor; @@ -106,7 +108,9 @@ public static WrapperExtensionContext buildContext( ) { // UI API var operatorMapper = new OperatorMapper(); - var criterionMapper = new CriterionMapper(operatorMapper); + var criterionOperatorMapper = new CriterionOperatorMapper(); + var criterionLiteralMapper = new CriterionLiteralMapper(); + var criterionMapper = new CriterionMapper(criterionOperatorMapper, criterionLiteralMapper); var literalMapper = new LiteralMapper(objectMapper); var atomicConstraintMapper = new AtomicConstraintMapper(literalMapper, operatorMapper); var policyValidator = new PolicyValidator(); @@ -114,12 +118,15 @@ public static WrapperExtensionContext buildContext( var policyMapper = new PolicyMapper( constraintExtractor, atomicConstraintMapper, - typeTransformerRegistry - ); + typeTransformerRegistry); + var edcPropertyUtils = new EdcPropertyUtils(); + var assetBuilder = new UiAssetMapper(edcPropertyUtils, jsonLd); + var assetMapper = new AssetMapper(typeTransformerRegistry, assetBuilder, jsonLd); var transferProcessStateService = new TransferProcessStateService(); var contractAgreementPageCardBuilder = new ContractAgreementPageCardBuilder( policyMapper, - transferProcessStateService + transferProcessStateService, + assetMapper ); var contractAgreementDataFetcher = new ContractAgreementDataFetcher( contractAgreementService, @@ -147,9 +154,7 @@ public static WrapperExtensionContext buildContext( transferProcessService); var contractNegotiationUtils = new ContractNegotiationUtils(contractNegotiationService); var contractAgreementUtils = new ContractAgreementUtils(contractAgreementService); - var edcPropertyUtils = new EdcPropertyUtils(); - var assetBuilder = new AssetBuilder(edcPropertyUtils); - var assetApiService = new AssetApiService(assetBuilder, assetService, edcPropertyUtils); + var assetApiService = new AssetApiService(assetService, assetMapper); var transferRequestBuilder = new TransferRequestBuilder( objectMapper, contractAgreementUtils, @@ -166,7 +171,6 @@ public static WrapperExtensionContext buildContext( policyMapper); var dataOfferBuilder = new DspDataOfferBuilder(jsonLd); var dspCatalogService = new DspCatalogService(catalogService, dataOfferBuilder); - var assetMapper = new AssetMapper(typeTransformerRegistry); var catalogApiService = new CatalogApiService(assetMapper, policyMapper, dspCatalogService); var contractOfferMapper = new ContractOfferMapper(policyMapper); var contractNegotiationBuilder = new ContractNegotiationBuilder(contractOfferMapper); diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java index ff5ce285d..171faef1e 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java @@ -16,24 +16,24 @@ import de.sovity.edc.ext.wrapper.api.common.model.AssetDto; import de.sovity.edc.ext.wrapper.api.common.model.PolicyDefinitionCreateRequest; -import de.sovity.edc.ext.wrapper.api.ui.model.AssetCreateRequest; +import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; import de.sovity.edc.ext.wrapper.api.ui.model.AssetPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionRequest; -import de.sovity.edc.ext.wrapper.api.ui.model.UiContractNegotiation; import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; import de.sovity.edc.ext.wrapper.api.ui.model.PolicyDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.TransferHistoryPage; +import de.sovity.edc.ext.wrapper.api.ui.model.UiContractNegotiation; import de.sovity.edc.ext.wrapper.api.ui.model.UiDataOffer; import de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.catalog.CatalogApiService; -import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementPageApiService; -import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementTransferApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.ContractDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_negotiations.ContractNegotiationApiService; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementPageApiService; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementTransferApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.policy.PolicyDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferHistoryPageApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferHistoryPageAssetFetcherService; @@ -116,8 +116,8 @@ public AssetPage assetPage() { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Operation(description = "Create a new Asset") - public IdResponseDto createAsset(AssetCreateRequest assetCreateRequest) { - return assetApiService.createAsset(assetCreateRequest); + public IdResponseDto createAsset(UiAssetCreateRequest uiAssetCreateRequest) { + return assetApiService.createAsset(uiAssetCreateRequest); } @DELETE diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetCreateRequest.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetCreateRequest.java deleted file mode 100644 index aacbb8121..000000000 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetCreateRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 sovity GmbH - * - * 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: - * sovity GmbH - initial API and implementation - * - */ - -package de.sovity.edc.ext.wrapper.api.ui.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Map; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Schema(description = "Data for creating an Asset") -public class AssetCreateRequest { - @Schema(description = "Data Address", requiredMode = Schema.RequiredMode.REQUIRED) - private Map dataAddressProperties; - - @Schema(description = "Properties of the Data Address", requiredMode = Schema.RequiredMode.REQUIRED) - private Map properties; - - @Schema(description = "Private Asset Properties", requiredMode = Schema.RequiredMode.REQUIRED) - private Map privateProperties; -} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetEntry.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetEntry.java deleted file mode 100644 index ce4b8fa06..000000000 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetEntry.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2022 sovity GmbH - * - * 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: - * sovity GmbH - initial API and implementation - * - */ - -package de.sovity.edc.ext.wrapper.api.ui.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.util.Map; - -@Data -@Schema(description = "Asset Entry for Asset Page") -public class AssetEntry { - @Schema(description = "Asset Properties", requiredMode = Schema.RequiredMode.REQUIRED) - private Map properties; - - @Schema(description = "Asset Private Properties", requiredMode = Schema.RequiredMode.REQUIRED) - private Map privateProperties; -} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetPage.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetPage.java index d801031bc..bcd792e22 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetPage.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/AssetPage.java @@ -13,7 +13,7 @@ */ package de.sovity.edc.ext.wrapper.api.ui.model; - +import de.sovity.edc.ext.wrapper.api.common.model.UiAsset; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; @@ -25,5 +25,5 @@ @Schema(description = "All data for the Asset Page") public class AssetPage { @Schema(description = "Visible Assets", requiredMode = Schema.RequiredMode.REQUIRED) - private List assets; + private List assets; } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementCard.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementCard.java index d9edf1340..4d5a33cc1 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementCard.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementCard.java @@ -14,7 +14,7 @@ package de.sovity.edc.ext.wrapper.api.ui.model; -import de.sovity.edc.ext.wrapper.api.common.model.AssetDto; +import de.sovity.edc.ext.wrapper.api.common.model.UiAsset; import de.sovity.edc.ext.wrapper.api.common.model.UiPolicy; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -58,7 +58,7 @@ public class ContractAgreementCard { private OffsetDateTime contractEndDate; @Schema(description = "Asset details", requiredMode = Schema.RequiredMode.REQUIRED) - private AssetDto asset; + private UiAsset asset; @Schema(description = "Contract Policy", requiredMode = Schema.RequiredMode.REQUIRED) private UiPolicy contractPolicy; diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionEntry.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionEntry.java index 1555ca523..a19650dcf 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionEntry.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionEntry.java @@ -33,5 +33,5 @@ public class ContractDefinitionEntry { private String contractPolicyId; @Schema(description = "Criteria for the contract", requiredMode = Schema.RequiredMode.REQUIRED) - private List assetSelector; + private List assetSelector; } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java index e726975c7..9d08d8bf6 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java @@ -43,5 +43,5 @@ public class ContractDefinitionRequest { private String accessPolicyId; @Schema(description = "List of Criteria for the contract", requiredMode = Schema.RequiredMode.REQUIRED) - private List assetSelector; + private List assetSelector; } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/IdResponseDto.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/IdResponseDto.java index f9716fa4c..78b1f17a0 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/IdResponseDto.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/IdResponseDto.java @@ -15,7 +15,11 @@ package de.sovity.edc.ext.wrapper.api.ui.model; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; import java.time.OffsetDateTime; diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionDto.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterion.java similarity index 79% rename from extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionDto.java rename to extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterion.java index 1da567428..465e4a300 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionDto.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterion.java @@ -15,20 +15,22 @@ package de.sovity.edc.ext.wrapper.api.ui.model; -import de.sovity.edc.ext.wrapper.api.common.model.OperatorDto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor @Schema(description = "Contract Definition Criterion as supported by the UI") -public class UiCriterionDto { - +public class UiCriterion { @Schema(description = "Left Operand", requiredMode = Schema.RequiredMode.REQUIRED) private String operandLeft; @Schema(description = "Operator", requiredMode = Schema.RequiredMode.REQUIRED) - private OperatorDto operator; + private UiCriterionOperator operator; @Schema(description = "Right Operand", requiredMode = Schema.RequiredMode.REQUIRED) - private UiCriterionLiteralDto operandRight; + private UiCriterionLiteral operandRight; } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteralDto.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteral.java similarity index 65% rename from extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteralDto.java rename to extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteral.java index aac875219..dfe5c44f5 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteralDto.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionLiteral.java @@ -12,11 +12,11 @@ @Getter @ToString -@Schema(description = "Criterion Literal DTO") +@Schema(description = "Criterion Literal") @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -public class UiCriterionLiteralDto { +public class UiCriterionLiteral { public enum CriterionLiteralTypeDto { VALUE, VALUE_LIST @@ -30,11 +30,11 @@ public enum CriterionLiteralTypeDto { @Schema(description = "Only for type VALUE_LIST. List of values, e.g. for the IN-Operator.") private List valueList; - public static UiCriterionLiteralDto ofValue(@NonNull String value) { - return new UiCriterionLiteralDto(CriterionLiteralTypeDto.VALUE, value, null); + public static UiCriterionLiteral ofValue(@NonNull String value) { + return new UiCriterionLiteral(CriterionLiteralTypeDto.VALUE, value, null); } - public static UiCriterionLiteralDto ofValueList(@NonNull List valueList) { - return new UiCriterionLiteralDto(CriterionLiteralTypeDto.VALUE_LIST, null, valueList); + public static UiCriterionLiteral ofValueList(@NonNull List valueList) { + return new UiCriterionLiteral(CriterionLiteralTypeDto.VALUE_LIST, null, valueList); } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionOperator.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionOperator.java new file mode 100644 index 000000000..766873c1f --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/UiCriterionOperator.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 sovity GmbH + * + * 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: + * sovity GmbH - init + */ + +package de.sovity.edc.ext.wrapper.api.ui.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Contract Definition Criterion + * + * @see org.eclipse.edc.connector.defaults.storage.CriterionToPredicateConverterImpl + */ +@Getter +@RequiredArgsConstructor +@Schema(description = "Operator for constraints") +public enum UiCriterionOperator { + EQ, + IN, + LIKE; +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java index 4f550c88e..78b4a17b3 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetApiService.java @@ -14,10 +14,10 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.asset; -import de.sovity.edc.ext.wrapper.api.ui.model.AssetCreateRequest; -import de.sovity.edc.ext.wrapper.api.ui.model.AssetEntry; +import de.sovity.edc.ext.wrapper.api.common.mappers.AssetMapper; +import de.sovity.edc.ext.wrapper.api.common.model.UiAsset; +import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.spi.asset.AssetService; import org.eclipse.edc.spi.query.QuerySpec; @@ -29,23 +29,18 @@ @RequiredArgsConstructor public class AssetApiService { - private final AssetBuilder assetBuilder; private final AssetService assetService; - private final EdcPropertyUtils edcPropertyUtils; + private final AssetMapper assetMapper; - public List getAssets() { + public List getAssets() { var assets = getAllAssets(); - return assets.stream().sorted(Comparator.comparing(Asset::getCreatedAt).reversed()).map(asset -> { - var entry = new AssetEntry(); - entry.setProperties(edcPropertyUtils.truncateToMapOfString(asset.getProperties())); - entry.setPrivateProperties(edcPropertyUtils.truncateToMapOfString(asset.getPrivateProperties())); - return entry; - }).toList(); + return assets.stream().sorted(Comparator.comparing(Asset::getCreatedAt).reversed()) + .map(assetMapper::buildUiAsset).toList(); } @NotNull - public IdResponseDto createAsset(AssetCreateRequest request) { - var asset = assetBuilder.buildAsset(request); + public IdResponseDto createAsset(UiAssetCreateRequest request) { + var asset = assetMapper.buildAsset(request); asset = assetService.create(asset).getContent(); return new IdResponseDto(asset.getId()); } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java deleted file mode 100644 index d07dd04a7..000000000 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/asset/AssetBuilder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 sovity GmbH - * - * 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: - * sovity GmbH - initial API and implementation - * - */ - -package de.sovity.edc.ext.wrapper.api.ui.pages.asset; - - -import de.sovity.edc.ext.wrapper.api.ui.model.AssetCreateRequest; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.types.domain.asset.Asset; - -@RequiredArgsConstructor -public class AssetBuilder { - private final EdcPropertyUtils edcPropertyUtils; - - public Asset buildAsset(AssetCreateRequest request) { - var assetProperties = edcPropertyUtils.toMapOfObject(request.getProperties()); - var privateProperties = edcPropertyUtils.toMapOfObject(request.getPrivateProperties()); - - return Asset.Builder.newInstance() - .id(request.getProperties().get(Asset.PROPERTY_ID)) - .properties(assetProperties) - .privateProperties(privateProperties) - .dataAddress(edcPropertyUtils.buildDataAddress(request.getDataAddressProperties())) - .build(); - } -} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/catalog/CatalogApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/catalog/CatalogApiService.java index 8195cbbbc..6bfa417fe 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/catalog/CatalogApiService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/catalog/CatalogApiService.java @@ -61,7 +61,7 @@ private UiContractOffer buildContractOffer(DspContractOffer contractOffer) { } private UiAsset buildUiAsset(DspDataOffer dataOffer) { - var asset = assetMapper.buildAssetFromAssetPropertiesJsonLd(dataOffer.getAssetPropertiesJsonLd()); + var asset = assetMapper.buildAssetFromDatasetProperties(dataOffer.getAssetPropertiesJsonLd()); return assetMapper.buildUiAsset(asset); } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionApiService.java index 2f2ec3af2..47a9d0be2 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionApiService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionApiService.java @@ -37,14 +37,18 @@ public List getContractDefinitions() { var definitions = getAllContractDefinitions(); return definitions.stream() .sorted(Comparator.comparing(ContractDefinition::getCreatedAt).reversed()) - .map(definition -> { - var entry = new ContractDefinitionEntry(); - entry.setContractDefinitionId(definition.getId()); - entry.setAccessPolicyId(definition.getAccessPolicyId()); - entry.setContractPolicyId(definition.getContractPolicyId()); - entry.setAssetSelector(criterionMapper.mapToCriterionDtos(definition.getAssetsSelector())); - return entry; - }).toList(); + .map(this::buildContractDefinitionEntry) + .toList(); + } + + @NotNull + private ContractDefinitionEntry buildContractDefinitionEntry(ContractDefinition definition) { + var entry = new ContractDefinitionEntry(); + entry.setContractDefinitionId(definition.getId()); + entry.setAccessPolicyId(definition.getAccessPolicyId()); + entry.setContractPolicyId(definition.getContractPolicyId()); + entry.setAssetSelector(criterionMapper.buildUiCriteria(definition.getAssetsSelector())); + return entry; } @NotNull diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionBuilder.java index 0962dfbff..b948eed0d 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/ContractDefinitionBuilder.java @@ -34,7 +34,7 @@ public ContractDefinition buildContractDefinition(ContractDefinitionRequest requ .id(contractDefinitionId) .contractPolicyId(contractPolicyId) .accessPolicyId(accessPolicyId) - .assetsSelector(criterionMapper.mapToCriteria(assetsSelector)) + .assetsSelector(criterionMapper.buildCriteria(assetsSelector)) .build(); } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapper.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapper.java new file mode 100644 index 000000000..d8b10088e --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 sovity GmbH + * + * 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: + * sovity GmbH - initial API and implementation + * + */ + +package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; + + +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionLiteral; +import lombok.RequiredArgsConstructor; + +import java.util.Collection; +import java.util.List; + +@RequiredArgsConstructor +public class CriterionLiteralMapper { + + public UiCriterionLiteral buildUiCriterionLiteral(Object value) { + if (value instanceof Collection) { + var list = getValueList((Collection) value); + return UiCriterionLiteral.ofValueList(list); + } + + return UiCriterionLiteral.ofValue(value.toString()); + } + + public Object getValue(UiCriterionLiteral dto) { + return switch (dto.getType()) { + case VALUE -> dto.getValue(); + case VALUE_LIST -> dto.getValueList(); + default -> throw new IllegalStateException("Unhandled %s: %s".formatted( + UiCriterionLiteral.CriterionLiteralTypeDto.class.getName(), + dto.getType() + )); + }; + } + + private List getValueList(Collection valueList) { + return valueList.stream().map(it -> it == null ? null : it.toString()).toList(); + } +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapper.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapper.java index 4db3a43c0..79191ebfd 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapper.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapper.java @@ -15,61 +15,40 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; -import de.sovity.edc.ext.wrapper.api.common.mappers.OperatorMapper; -import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionDto; -import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionLiteralDto; +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterion; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.eclipse.edc.spi.query.Criterion; -import java.util.Collection; import java.util.List; +import java.util.Objects; @RequiredArgsConstructor public class CriterionMapper { - private final OperatorMapper operatorMapper; + private final CriterionOperatorMapper criterionOperatorMapper; + private final CriterionLiteralMapper criterionLiteralMapper; - public List mapToCriterionDtos(@NonNull List criteria) { - return criteria.stream().map(this::mapToCriterionDto).toList(); + public List buildUiCriteria(@NonNull List criteria) { + return criteria.stream().filter(Objects::nonNull).map(this::buildUiCriterion).toList(); } - UiCriterionDto mapToCriterionDto(Criterion criterion) { - if (criterion == null) { - return null; - } - UiCriterionDto dto = new UiCriterionDto(); - UiCriterionLiteralDto literalDto = buildCriterionLiteral(criterion.getOperandRight()); - dto.setOperandLeft(String.valueOf(criterion.getOperandLeft())); - dto.setOperator(operatorMapper.getOperatorDto(criterion.getOperator())); - dto.setOperandRight(literalDto); - return dto; - } + public UiCriterion buildUiCriterion(Criterion criterion) { + var operandLeft = String.valueOf(criterion.getOperandLeft()); + var operator = criterionOperatorMapper.getUiCriterionOperator(criterion.getOperator()); + var operandRight = criterionLiteralMapper.buildUiCriterionLiteral(criterion.getOperandRight()); - public Criterion mapToCriterion(@NonNull UiCriterionDto criterionDto) { - return new Criterion( - criterionDto.getOperandLeft(), - operatorMapper.getOperator(criterionDto.getOperator()).getOdrlRepresentation(), - criterionDto.getOperandRight()); + return new UiCriterion(operandLeft, operator, operandRight); } - public List mapToCriteria(@NonNull List criterionDtos) { - return criterionDtos.stream().map(this::mapToCriterion).toList(); + public List buildCriteria(@NonNull List criteria) { + return criteria.stream().filter(Objects::nonNull).map(this::buildCriterion).toList(); } + public Criterion buildCriterion(@NonNull UiCriterion criterionDto) { + var operandLeft = criterionDto.getOperandLeft(); + var operator = criterionOperatorMapper.getCriterionOperator(criterionDto.getOperator()); + var operandRight = criterionLiteralMapper.getValue(criterionDto.getOperandRight()); - UiCriterionLiteralDto buildCriterionLiteral(Object value) { - if (value instanceof Collection) { - var list = ((Collection) value).stream().map(it -> it == null ? null : it.toString()).toList(); - return UiCriterionLiteralDto.ofValueList(list); - } - - return UiCriterionLiteralDto.ofValue(value.toString()); - } - - Object readCriterionLiteral(UiCriterionLiteralDto dto) { - if (dto.getType() == UiCriterionLiteralDto.CriterionLiteralTypeDto.VALUE_LIST) { - return dto.getValueList(); - } - return dto.getValue(); + return new Criterion(operandLeft, operator, operandRight); } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapper.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapper.java new file mode 100644 index 000000000..f44deccf6 --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapper.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 sovity GmbH + * + * 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: + * sovity GmbH - initial API and implementation + * + */ + +package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; + + +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionOperator; +import de.sovity.edc.ext.wrapper.utils.MapUtils; +import lombok.RequiredArgsConstructor; + +import java.util.Map; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +@RequiredArgsConstructor +public class CriterionOperatorMapper { + /** + * @see org.eclipse.edc.connector.defaults.storage.CriterionToPredicateConverterImpl + */ + private final Map mappings = Map.of( + UiCriterionOperator.EQ, "=", + UiCriterionOperator.LIKE, "like", + UiCriterionOperator.IN, "in" + ); + + private final Map reverseMappings = MapUtils.reverse(mappings); + + public String getCriterionOperator(UiCriterionOperator operator) { + String result = mappings.get(operator); + return requireNonNull(result, () -> "Unhandled %s: %s".formatted( + UiCriterionOperator.class.getName(), operator)); + } + + public UiCriterionOperator getUiCriterionOperator(String operator) { + UiCriterionOperator result = reverseMappings.get(operator == null ? null : operator.toLowerCase()); + return requireNonNull(result, () -> "Could not find %s for: %s".formatted( + UiCriterionOperator.class.getName(), operator)); + } +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractAgreementPageCardBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractAgreementPageCardBuilder.java index 54715b35f..f75c568fc 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractAgreementPageCardBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractAgreementPageCardBuilder.java @@ -14,8 +14,8 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services; +import de.sovity.edc.ext.wrapper.api.common.mappers.AssetMapper; import de.sovity.edc.ext.wrapper.api.common.mappers.PolicyMapper; -import de.sovity.edc.ext.wrapper.api.common.model.AssetDto; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementCard; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementDirection; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferProcess; @@ -34,13 +34,13 @@ import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcMillisToOffsetDateTime; import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcSecondsToOffsetDateTime; -import static de.sovity.edc.ext.wrapper.utils.MapUtils.mapValues; @Slf4j @RequiredArgsConstructor public class ContractAgreementPageCardBuilder { private final PolicyMapper policyMapper; private final TransferProcessStateService transferProcessStateService; + private final AssetMapper assetMapper; @NotNull public ContractAgreementCard buildContractAgreementCard( @@ -56,7 +56,7 @@ public ContractAgreementCard buildContractAgreementCard( card.setCounterPartyAddress(negotiation.getCounterPartyAddress()); card.setCounterPartyId(negotiation.getCounterPartyId()); card.setContractSigningDate(utcSecondsToOffsetDateTime(agreement.getContractSigningDate())); - card.setAsset(buildAssetDto(asset)); + card.setAsset(assetMapper.buildUiAsset(asset)); card.setContractPolicy(policyMapper.buildUiPolicy(agreement.getPolicy())); card.setTransferProcesses(buildTransferProcesses(transferProcesses)); return card; @@ -85,11 +85,4 @@ private ContractAgreementTransferProcess buildContractAgreementTransfer( transferProcess.setErrorMessage(transferProcessEntity.getErrorDetail()); return transferProcess; } - - @NotNull - private AssetDto buildAssetDto(@NonNull Asset asset) { - var createdAt = utcMillisToOffsetDateTime(asset.getCreatedAt()); - var properties = mapValues(asset.getProperties(), Object::toString); - return new AssetDto(asset.getId(), createdAt, properties); - } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/TransferRequestBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/TransferRequestBuilder.java index 53ca11123..ea093aa91 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/TransferRequestBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/TransferRequestBuilder.java @@ -15,9 +15,9 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services; import com.fasterxml.jackson.databind.ObjectMapper; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequestParams; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingService.java index ef5149cbd..ad2f96c22 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingService.java @@ -1,10 +1,10 @@ package de.sovity.edc.ext.wrapper.api.usecase.services; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; import de.sovity.edc.ext.wrapper.api.usecase.model.AssetEntryDto; import de.sovity.edc.ext.wrapper.api.usecase.model.ContractDefinitionRequestDto; import de.sovity.edc.ext.wrapper.api.usecase.model.CreateOfferingDto; import de.sovity.edc.ext.wrapper.api.usecase.model.PolicyDefinitionRequestDto; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/MapUtils.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/MapUtils.java index 63456c96b..b7bc9d297 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/MapUtils.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/utils/MapUtils.java @@ -36,4 +36,8 @@ public static Map associateBy(Collection collection, Function Map reverse(@NonNull Map map) { + return map.entrySet().stream().collect(toMap(Map.Entry::getValue, Map.Entry::getKey)); + } } diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapperTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapperTest.java new file mode 100644 index 000000000..e9ea4773e --- /dev/null +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionLiteralMapperTest.java @@ -0,0 +1,54 @@ +package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; + +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionLiteral; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class CriterionLiteralMapperTest { + private CriterionLiteralMapper criterionLiteralMapper; + + @BeforeEach + void setup() { + criterionLiteralMapper = new CriterionLiteralMapper(); + } + + @Test + void testBuildUiCriterionLiteral_String() { + String value = "testValue"; + UiCriterionLiteral literal = criterionLiteralMapper.buildUiCriterionLiteral(value); + + assertThat(literal.getType()).isEqualTo(UiCriterionLiteral.CriterionLiteralTypeDto.VALUE); + assertThat(literal.getValue()).isEqualTo(value); + assertThat(literal.getValueList()).isNull(); + } + + @Test + void testBuildUiCriterionLiteral_StringList() { + List valueList = Arrays.asList("value1", "value2", null); + UiCriterionLiteral literal = criterionLiteralMapper.buildUiCriterionLiteral(valueList); + + assertThat(literal.getType()).isEqualTo(UiCriterionLiteral.CriterionLiteralTypeDto.VALUE_LIST); + assertThat(literal.getValueList()).containsExactly("value1", "value2", null); + assertThat(literal.getValue()).isNull(); + } + + @Test + void testGetValue_String() { + String value = "testValue"; + UiCriterionLiteral literal = UiCriterionLiteral.ofValue(value); + assertThat(criterionLiteralMapper.getValue(literal)).isEqualTo(value); + } + + @Test + void testGetValue_StringList() { + List valueList = Arrays.asList("value1", "value2"); + UiCriterionLiteral literal = UiCriterionLiteral.ofValueList(valueList); + assertThat(criterionLiteralMapper.getValue(literal)).isEqualTo(valueList); + } +} + diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapperTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapperTest.java index 8e5f1c1b1..367408088 100644 --- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapperTest.java +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionMapperTest.java @@ -1,81 +1,44 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; -import de.sovity.edc.ext.wrapper.api.common.mappers.OperatorMapper; -import de.sovity.edc.ext.wrapper.api.common.model.OperatorDto; -import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionDto; -import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionLiteralDto; -import org.eclipse.edc.policy.model.Operator; +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterion; +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionLiteral; +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionOperator; import org.eclipse.edc.spi.query.Criterion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; class CriterionMapperTest { private CriterionMapper criterionMapper; - private OperatorMapper operatorMapper; @BeforeEach void setup() { - operatorMapper = new OperatorMapper(); - criterionMapper = new CriterionMapper(operatorMapper); + criterionMapper = new CriterionMapper(new CriterionOperatorMapper(), new CriterionLiteralMapper()); } @Test void testMappingFromCriterionToDto() { - Criterion criterion = new Criterion("operandLeft", "EQ", "operandRight"); - UiCriterionDto dto = criterionMapper.mapToCriterionDto(criterion); + Criterion criterion = new Criterion("left", "=", "right"); + UiCriterion dto = criterionMapper.buildUiCriterion(criterion); - assertThat(dto.getOperandLeft()).isEqualTo(criterion.getOperandLeft()); - assertThat(dto.getOperator()).isEqualTo(operatorMapper.getOperatorDto(criterion.getOperator())); - assertThat(dto.getOperandRight().getValue()).isEqualTo(criterion.getOperandRight()); + assertThat(dto.getOperandLeft()).isEqualTo("left"); + assertThat(dto.getOperator()).isEqualTo(UiCriterionOperator.EQ); + assertThat(dto.getOperandRight().getValue()).isEqualTo("right"); } @Test void testMappingFromDtoToCriterion() { - UiCriterionDto dto = new UiCriterionDto(); - dto.setOperandLeft("operandLeft"); - dto.setOperator(OperatorDto.EQ); - dto.setOperandRight(UiCriterionLiteralDto.ofValue("operandRight")); - - Criterion criterion = criterionMapper.mapToCriterion(dto); - - assertThat(criterion.getOperandLeft()).isEqualTo(dto.getOperandLeft()); - assertThat(criterion.getOperator()).isEqualTo(Operator.EQ.getOdrlRepresentation()); - assertThat(criterion.getOperandRight()).isEqualTo(dto.getOperandRight()); - } - - @Test - void testBuildCriterionLiteral() { - String value = "testValue"; - UiCriterionLiteralDto dtowithValue = criterionMapper.buildCriterionLiteral(value); - - List valueList = Arrays.asList("value1", "value2", null); - UiCriterionLiteralDto dtowithList = criterionMapper.buildCriterionLiteral(valueList); - - assertThat(dtowithValue.getType()).isEqualTo(UiCriterionLiteralDto.CriterionLiteralTypeDto.VALUE); - assertThat(dtowithValue.getValue()).isEqualTo(value); - assertThat(dtowithValue.getValueList()).isNull(); - - assertThat(dtowithList.getType()).isEqualTo(UiCriterionLiteralDto.CriterionLiteralTypeDto.VALUE_LIST); - assertThat(dtowithList.getValueList()).containsExactly("value1", "value2", null); - assertThat(dtowithList.getValue()).isNull(); - } - - @Test - void testReadCriterionLiteral() { - String value = "testValue"; - UiCriterionLiteralDto dtowithValue = UiCriterionLiteralDto.ofValue(value); - - List valueList = Arrays.asList("value1", "value2"); - UiCriterionLiteralDto dtowithList = UiCriterionLiteralDto.ofValueList(valueList); + UiCriterion dto = new UiCriterion(); + dto.setOperandLeft("left"); + dto.setOperator(UiCriterionOperator.EQ); + dto.setOperandRight(UiCriterionLiteral.ofValue("right")); - assertThat(criterionMapper.readCriterionLiteral(dtowithValue)).isEqualTo(value); - assertThat(criterionMapper.readCriterionLiteral(dtowithList)).isEqualTo(valueList); + Criterion criterion = criterionMapper.buildCriterion(dto); + assertThat(criterion.getOperandLeft()).isEqualTo("left"); + assertThat(criterion.getOperator()).isEqualTo("="); + assertThat(criterion.getOperandRight()).isEqualTo("right"); } } diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapperTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapperTest.java new file mode 100644 index 000000000..d2915f2c9 --- /dev/null +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_definitions/CriterionOperatorMapperTest.java @@ -0,0 +1,38 @@ +package de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions; + +import de.sovity.edc.ext.wrapper.api.ui.model.UiCriterionOperator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +class CriterionOperatorMapperTest { + private CriterionOperatorMapper criterionOperatorMapper; + + @BeforeEach + void setup() { + criterionOperatorMapper = new CriterionOperatorMapper(); + } + + @Test + void testCaseInsensitivity() { + assertThat(criterionOperatorMapper.getUiCriterionOperator("lIKe")).isEqualTo(UiCriterionOperator.LIKE); + } + + @Test + void testMappings() { + assertThat(criterionOperatorMapper.getUiCriterionOperator("=")).isEqualTo(UiCriterionOperator.EQ); + assertThat(criterionOperatorMapper.getUiCriterionOperator("like")).isEqualTo(UiCriterionOperator.LIKE); + assertThat(criterionOperatorMapper.getUiCriterionOperator("in")).isEqualTo(UiCriterionOperator.IN); + assertThat(criterionOperatorMapper.getCriterionOperator(UiCriterionOperator.EQ)).isEqualTo("="); + assertThat(criterionOperatorMapper.getCriterionOperator(UiCriterionOperator.LIKE)).isEqualTo("like"); + assertThat(criterionOperatorMapper.getCriterionOperator(UiCriterionOperator.IN)).isEqualTo("in"); + + // Ensures the mapping isn't forgotten in the future + Arrays.stream(UiCriterionOperator.values()).forEach(criterionOperatorMapper::getCriterionOperator); + } + +} + diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingServiceTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingServiceTest.java index 67885b5a0..8bd1dbbfc 100644 --- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingServiceTest.java +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/usecase/services/OfferingServiceTest.java @@ -1,12 +1,12 @@ package de.sovity.edc.ext.wrapper.api.usecase.services; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; import de.sovity.edc.ext.wrapper.api.common.model.PermissionDto; import de.sovity.edc.ext.wrapper.api.common.model.PolicyDto; import de.sovity.edc.ext.wrapper.api.usecase.model.AssetEntryDto; import de.sovity.edc.ext.wrapper.api.usecase.model.ContractDefinitionRequestDto; import de.sovity.edc.ext.wrapper.api.usecase.model.CreateOfferingDto; import de.sovity.edc.ext.wrapper.api.usecase.model.PolicyDefinitionRequestDto; -import de.sovity.edc.ext.wrapper.utils.EdcPropertyUtils; import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore; diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/MapUtilsTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/MapUtilsTest.java new file mode 100644 index 000000000..cf8b53fd8 --- /dev/null +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/utils/MapUtilsTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 sovity GmbH + * + * 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: + * sovity GmbH - initial API and implementation + * + */ + +package de.sovity.edc.ext.wrapper.utils; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class MapUtilsTest { + + @Test + void mapValues() { + assertThat(MapUtils.mapValues(Map.of(1, "a"), String::toUpperCase)).isEqualTo(Map.of(1, "A")); + } + + @Test + void associateBy() { + assertThat(MapUtils.associateBy(List.of("a"), String::toUpperCase)).isEqualTo(Map.of("A", "a")); + } + + @Test + void reverse() { + assertThat(MapUtils.reverse(Map.of("a", 1))).isEqualTo(Map.of(1, "a")); + } +} diff --git a/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java index ef5f1c5a7..77d603022 100644 --- a/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java +++ b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java @@ -16,22 +16,30 @@ import de.sovity.edc.client.EdcClient; import de.sovity.edc.client.gen.model.ContractAgreementTransferRequest; import de.sovity.edc.client.gen.model.ContractAgreementTransferRequestParams; +import de.sovity.edc.client.gen.model.ContractDefinitionRequest; import de.sovity.edc.client.gen.model.ContractNegotiationRequest; import de.sovity.edc.client.gen.model.ContractNegotiationState.SimplifiedStateEnum; +import de.sovity.edc.client.gen.model.PolicyDefinitionCreateRequest; +import de.sovity.edc.client.gen.model.UiAssetCreateRequest; import de.sovity.edc.client.gen.model.UiContractNegotiation; import de.sovity.edc.client.gen.model.UiContractOffer; +import de.sovity.edc.client.gen.model.UiCriterion; +import de.sovity.edc.client.gen.model.UiCriterionLiteral; import de.sovity.edc.client.gen.model.UiDataOffer; +import de.sovity.edc.client.gen.model.UiPolicyCreateRequest; import de.sovity.edc.extension.e2e.connector.ConnectorRemote; import de.sovity.edc.extension.e2e.connector.MockDataAddressRemote; import de.sovity.edc.extension.e2e.db.TestDatabase; import de.sovity.edc.extension.e2e.db.TestDatabaseFactory; +import de.sovity.edc.utils.jsonld.vocab.Prop; import org.awaitility.Awaitility; import org.eclipse.edc.junit.extensions.EdcExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.util.UUID; +import java.util.List; +import java.util.Map; import static de.sovity.edc.extension.e2e.connector.DataTransferTestUtil.validateDataTransferred; import static de.sovity.edc.extension.e2e.connector.config.ConnectorConfigFactory.forTestDatabase; @@ -56,6 +64,7 @@ class UiApiWrapperTest { private ConnectorRemote providerConnector; private ConnectorRemote consumerConnector; + private EdcClient providerClient; private EdcClient consumerClient; private MockDataAddressRemote dataAddress; @@ -65,6 +74,11 @@ void setup() { providerEdcContext.setConfiguration(providerConfig.getProperties()); providerConnector = new ConnectorRemote(fromConnectorConfig(providerConfig)); + providerClient = EdcClient.builder() + .managementApiUrl(providerConfig.getManagementEndpoint().getUri().toString()) + .managementApiKey(providerConfig.getProperties().get("edc.api.auth.key")) + .build(); + var consumerConfig = forTestDatabase(CONSUMER_PARTICIPANT_ID, 23000, CONSUMER_DATABASE); consumerEdcContext.setConfiguration(consumerConfig.getProperties()); consumerConnector = new ConnectorRemote(fromConnectorConfig(consumerConfig)); @@ -82,12 +96,52 @@ void setup() { @Test void provide_consume_assetMapping_policyMapping() { // arrange - // TODO use UI API for creation of the data offer - // TODO use a policy with a constraint - // TODO test all asset properties including additionalProperties var data = "expected data 123"; - var assetId = UUID.randomUUID().toString(); - providerConnector.createDataOffer(assetId, dataAddress.getDataSourceUrl(data)); + + var policyId = providerClient.uiApi().createPolicyDefinition(PolicyDefinitionCreateRequest.builder() + .policyDefinitionId("policy-1") + .policy(UiPolicyCreateRequest.builder() + .constraints(List.of()) + .build()) + .build()).getId(); + + var assetId = providerClient.uiApi().createAsset(UiAssetCreateRequest.builder() + .id("asset-1") + .name("AssetName") + .description("AssetDescription") + .licenseUrl("https://license-url") + .version("1.0.0") + .language("en") + .mediaType("application/json") + .dataCategory("dataCategory") + .dataSubcategory("dataSubcategory") + .dataModel("dataModel") + .geoReferenceMethod("geoReferenceMethod") + .transportMode("transportMode") + .keywords(List.of("keyword1", "keyword2")) + .creatorOrganizationName("creatorOrganizationName") + .publisherHomepage("publisherHomepage") + .dataAddressProperties(Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.METHOD, "GET", + Prop.Edc.BASE_URL, dataAddress.getDataSourceUrl(data) + )) + .build()).getId(); + assertThat(assetId).isEqualTo("asset-1"); + + providerClient.uiApi().createContractDefinition(ContractDefinitionRequest.builder() + .contractDefinitionId("cd-1") + .accessPolicyId(policyId) + .contractPolicyId(policyId) + .assetSelector(List.of(UiCriterion.builder() + .operandLeft(Prop.Edc.ID) + .operator(UiCriterion.OperatorEnum.EQ) + .operandRight(UiCriterionLiteral.builder() + .type(UiCriterionLiteral.TypeEnum.VALUE) + .value(assetId) + .build()) + .build())) + .build()); var dataOffers = consumerClient.uiApi().catalogPageDataOffers(getProtocolEndpoint(providerConnector)); assertThat(dataOffers).hasSize(1); @@ -103,6 +157,25 @@ void provide_consume_assetMapping_policyMapping() { assertThat(dataOffer.getEndpoint()).isEqualTo(getProtocolEndpoint(providerConnector)); assertThat(dataOffer.getParticipantId()).isEqualTo(PROVIDER_PARTICIPANT_ID); assertThat(dataOffer.getAsset().getAssetId()).isEqualTo(assetId); + assertThat(dataOffer.getAsset().getKeywords()).isEqualTo(List.of("keyword1", "keyword2")); + assertThat(dataOffer.getAsset().getName()).isEqualTo("AssetName"); + assertThat(dataOffer.getAsset().getDescription()).isEqualTo("AssetDescription"); + assertThat(dataOffer.getAsset().getVersion()).isEqualTo("1.0.0"); + assertThat(dataOffer.getAsset().getLanguage()).isEqualTo("en"); + assertThat(dataOffer.getAsset().getMediaType()).isEqualTo("application/json"); + assertThat(dataOffer.getAsset().getDataCategory()).isEqualTo("dataCategory"); + assertThat(dataOffer.getAsset().getDataSubcategory()).isEqualTo("dataSubcategory"); + assertThat(dataOffer.getAsset().getDataModel()).isEqualTo("dataModel"); + assertThat(dataOffer.getAsset().getGeoReferenceMethod()).isEqualTo("geoReferenceMethod"); + assertThat(dataOffer.getAsset().getTransportMode()).isEqualTo("transportMode"); + assertThat(dataOffer.getAsset().getLicenseUrl()).isEqualTo("https://license-url"); + assertThat(dataOffer.getAsset().getKeywords()).isEqualTo(List.of("keyword1", "keyword2")); + assertThat(dataOffer.getAsset().getCreatorOrganizationName()).isEqualTo("creatorOrganizationName"); + assertThat(dataOffer.getAsset().getPublisherHomepage()).isEqualTo("publisherHomepage"); + assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyMethod()).isFalse(); + assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyPath()).isFalse(); + assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyQueryParams()).isFalse(); + assertThat(dataOffer.getAsset().getHttpDatasourceHintsProxyBody()).isFalse(); validateDataTransferred(dataAddress.getDataSinkSpyUrl(), data); } diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/JsonUtils.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/JsonUtils.java index 26a8258e5..3c011ce2d 100644 --- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/JsonUtils.java +++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/JsonUtils.java @@ -32,6 +32,10 @@ public static JsonObject parseJsonObj(String string) { } public static String toJson(JsonValue json) { + if (json == null) { + return "null"; + } + var sw = new StringWriter(); try (var writer = Json.createWriter(sw)) { writer.write(json); diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java index 0ff22fab9..7b6169c94 100644 --- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java +++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java @@ -13,6 +13,7 @@ package de.sovity.edc.utils.jsonld; +import de.sovity.edc.utils.JsonUtils; import jakarta.json.JsonNumber; import jakarta.json.JsonObject; import jakarta.json.JsonString; @@ -48,13 +49,37 @@ public static String string(JsonValue json) { } return switch (value.getValueType()) { - case ARRAY -> throw new IllegalStateException("unexpected array" + value); - case OBJECT -> throw new IllegalStateException("unexpected object" + value); case STRING -> ((JsonString) value).getString(); case NUMBER -> ((JsonNumber) value).bigDecimalValue().toString(); case FALSE -> "false"; case TRUE -> "true"; case NULL -> null; + // We do this over throwing errors because we want to be able to handle invalid json-ld + case ARRAY, OBJECT -> JsonUtils.toJson(value); + }; + } + + /** + * Get a boolean property + * + * @param json json-ld + * @return boolean value or null + */ + public static Boolean bool(JsonValue json) { + var value = value(json); + if (value == null) { + return null; + } + + return switch (value.getValueType()) { + case STRING -> switch (((JsonString) value).getString().toLowerCase()) { + case "true" -> Boolean.TRUE; + case "false" -> Boolean.FALSE; + default -> null; + }; + case FALSE -> Boolean.FALSE; + case TRUE -> Boolean.TRUE; + case NUMBER, NULL, ARRAY, OBJECT -> null; }; } @@ -128,6 +153,27 @@ public static String string(JsonObject object, String key) { return string(field); } + /** + * Get an object property. Defaults to an empty object for ease of use if not found. + * + * @param object json-ld + * @param key key + * @return string or null + */ + public static JsonObject object(JsonObject object, String key) { + JsonValue field = object.get(key); + if (field == null) { + return JsonValue.EMPTY_JSON_OBJECT; + } + + var unwrapped = value(field); + if (unwrapped == null || unwrapped.getValueType() != JsonValue.ValueType.OBJECT) { + return JsonValue.EMPTY_JSON_OBJECT; + } + + return (JsonObject) unwrapped; + } + /** * Get a list property while unwrapping values and only keeping objects. * @@ -142,4 +188,36 @@ public static List listOfObjects(JsonObject object, String key) { } return listOfObjects(field); } + + /** + * Get a list of strings. defaults to empty list + * + * @param object json-ld + * @param key key + * @return string list or empty list + */ + public static List stringList(JsonObject object, String key) { + JsonValue field = object.get(key); + if (field == null) { + return List.of(); + } + return list(field).stream() + .map(JsonLdUtils::string) + .toList(); + } + + /** + * Get a boolean property. defaults to null + * + * @param object json-ld + * @param key key + * @return boolean or null + */ + public static Boolean bool(JsonObject object, String key) { + JsonValue field = object.get(key); + if (field == null) { + return null; + } + return bool(field); + } } diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java index 8f9de2574..948528107 100644 --- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java +++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java @@ -19,14 +19,27 @@ public class Prop { public final String ID = "@id"; public final String TYPE = "@type"; + public final String VALUE = "@value"; + public final String CONTEXT = "@context"; + public final String LANGUAGE = "@language"; + public final String PROPERTIES = "properties"; @UtilityClass public class Edc { public final String CTX = "https://w3id.org/edc/v0.0.1/ns/"; public final String TYPE_ASSET = CTX + "Asset"; + public final String TYPE_DATA_ADDRESS = CTX + "DataAddress"; public final String ID = CTX + "id"; public final String PARTICIPANT_ID = CTX + "participantId"; public final String PROPERTIES = CTX + "properties"; + public final String DATA_ADDRESS = CTX + "dataAddress"; + public final String TYPE = CTX + "type"; + public final String BASE_URL = CTX + "baseUrl"; + public final String METHOD = CTX + "method"; + public final String PROXY_METHOD = CTX + "proxyMethod"; + public final String PROXY_PATH = CTX + "proxyPath"; + public final String PROXY_QUERY_PARAMS = CTX + "proxyQueryParams"; + public final String PROXY_BODY = CTX + "proxyBody"; } /** @@ -47,6 +60,9 @@ public class Dcat { public final String DATASET = CTX_WRONG_BUT_USED_BY_CORE_EDC + "dataset"; public final String DISTRIBUTION = CTX_WRONG_BUT_USED_BY_CORE_EDC + "distribution"; public final String VERSION = CTX + "version"; + public final String KEYWORDS = CTX + "keyword"; + public final String LANDING_PAGE = CTX + "landingPage"; + public final String MEDIATYPE = CTX + "mediaType"; } /** @@ -57,4 +73,59 @@ public class Odrl { public final String CTX = "http://www.w3.org/ns/odrl/2/"; public final String HAS_POLICY = CTX + "hasPolicy"; } + + /** + * Dcterms Metadata Terms Vocabulary, see DCMI Metadata Terms + */ + @UtilityClass + public class Dcterms { + public final String CTX = "http://purl.org/dc/terms/"; + public final String IDENTIFIER = CTX + "identifier"; + public final String NAME = CTX + "title"; + public final String DESCRIPTION = CTX + "description"; + public final String LANGUAGE = CTX + "language"; + public final String CREATOR = CTX + "creator"; + public final String PUBLISHER = CTX + "publisher"; + public final String LICENSE = CTX + "license"; + } + + /** + * Dcterms Metadata Terms Vocabulary, see DCAT 3 Specification + */ + @UtilityClass + public class SovityDcatExt { + public final String CTX = "https://semantic.sovity.io/dcat-ext#"; + + @UtilityClass + public class HttpDatasourceHints { + public final String METHOD = CTX + "httpDatasourceHintsProxyMethod"; + public final String PATH = CTX + "httpDatasourceHintsProxyPath"; + public final String QUERY_PARAMS = CTX + "httpDatasourceHintsProxyQueryParams"; + public final String BODY = CTX + "httpDatasourceHintsProxyBody"; + } + } + + /** + * FOAF Vocabulary + */ + @UtilityClass + public class Foaf { + public final String CTX = "http://xmlns.com/foaf/0.1/"; + public final String ORGANIZATION = CTX + "Organization"; + public final String NAME = CTX + "name"; + public final String HOMEPAGE = CTX + "homepage"; + } + + /** + * MDS Vocabulary + */ + @UtilityClass + public class Mds { + public final String CTX = "http://w3id.org/mds#"; + public final String DATA_CATEGORY = CTX + "dataCategory"; + public final String DATA_SUBCATEGORY = CTX + "dataSubcategory"; + public final String DATA_MODEL = CTX + "dataModel"; + public final String GEO_REFERENCE_METHOD = CTX + "geoReferenceMethod"; + public final String TRANSPORT_MODE = CTX + "transportMode"; + } } diff --git a/utils/test-connector-remote/build.gradle.kts b/utils/test-connector-remote/build.gradle.kts index 38b44059b..53563830f 100644 --- a/utils/test-connector-remote/build.gradle.kts +++ b/utils/test-connector-remote/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { api("${edcGroup}:junit:${edcVersion}") api("org.awaitility:awaitility:${awaitilityVersion}") + api(project(":utils:json-and-jsonld-utils")) implementation("${edcGroup}:sql-core:${edcVersion}") implementation("${edcGroup}:json-ld-spi:${edcVersion}") implementation("${edcGroup}:json-ld:${edcVersion}")