diff --git a/extensions/control-plane/api/management-api/edr-cache-api/build.gradle.kts b/extensions/control-plane/api/management-api/edr-cache-api/build.gradle.kts new file mode 100644 index 00000000000..2047c4b2b18 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + api(project(":spi:common:edr-store-spi")) + api(project(":spi:common:validator-spi")) + api(project(":spi:control-plane:control-plane-spi")) + implementation(project(":extensions:common:api:api-core")) + implementation(project(":extensions:common:api:management-api-configuration")) + implementation(project(":core:common:validator-core")) + + implementation(libs.jakarta.rsApi) + + testImplementation(project(":core:common:transform-core")) + testImplementation(project(":core:control-plane:control-plane-core")) + testImplementation(project(":core:data-plane-selector:data-plane-selector-core")) + testImplementation(project(":extensions:common:http")) + testImplementation(project(":core:common:junit")) + testImplementation(testFixtures(project(":extensions:common:http:jersey-core"))) + testImplementation(libs.restAssured) + testImplementation(libs.awaitility) +} + +edcBuild { + swagger { + apiGroup.set("management-api") + } +} + + diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/EdrCacheApiExtension.java b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/EdrCacheApiExtension.java new file mode 100644 index 00000000000..353a5a9020e --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/EdrCacheApiExtension.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr; + +import jakarta.json.Json; +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.connector.api.management.edr.transform.JsonObjectFromEndpointDataReferenceEntryTransformer; +import org.eclipse.edc.connector.api.management.edr.v1.EdrCacheApiController; +import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.spi.WebService; + +import java.util.Map; + +import static org.eclipse.edc.connector.api.management.edr.EdrCacheApiExtension.NAME; + +@Extension(NAME) +public class EdrCacheApiExtension implements ServiceExtension { + + public static final String NAME = "Management API: EDR cache"; + + @Inject + private WebService webService; + + @Inject + private ManagementApiConfiguration config; + + @Inject + private TypeTransformerRegistry transformerRegistry; + @Inject + private JsonObjectValidatorRegistry validator; + + @Inject + private EndpointDataReferenceStore edrStore; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var jsonFactory = Json.createBuilderFactory(Map.of()); + var managementTypeTransformerRegistry = transformerRegistry.forContext("management-api"); + + managementTypeTransformerRegistry.register(new JsonObjectFromEndpointDataReferenceEntryTransformer(jsonFactory)); + + webService.registerResource(config.getContextAlias(), new EdrCacheApiController(edrStore, + managementTypeTransformerRegistry, validator, monitor)); + } +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java new file mode 100644 index 00000000000..e3502b16cc1 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.transform; + +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CONTRACT_NEGOTIATION_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CREATED_AT; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_PROVIDER_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TRANSFER_PROCESS_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; + +public class JsonObjectFromEndpointDataReferenceEntryTransformer extends AbstractJsonLdTransformer { + private final JsonBuilderFactory jsonFactory; + + + public JsonObjectFromEndpointDataReferenceEntryTransformer(JsonBuilderFactory jsonFactory) { + super(EndpointDataReferenceEntry.class, JsonObject.class); + this.jsonFactory = jsonFactory; + } + + @Override + public @Nullable JsonObject transform(@NotNull EndpointDataReferenceEntry entry, @NotNull TransformerContext context) { + return jsonFactory.createObjectBuilder() + .add(ID, entry.getId()) + .add(TYPE, EDR_ENTRY_TYPE) + .add(EDR_ENTRY_PROVIDER_ID, entry.getProviderId()) + .add(EDR_ENTRY_ASSET_ID, entry.getAssetId()) + .add(EDR_ENTRY_AGREEMENT_ID, entry.getAgreementId()) + .add(EDR_ENTRY_TRANSFER_PROCESS_ID, entry.getTransferProcessId()) + .add(EDR_ENTRY_CREATED_AT, entry.getCreatedAt()) + .add(EDR_ENTRY_CONTRACT_NEGOTIATION_ID, entry.getContractNegotiationId()) + .build(); + } +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApi.java b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApi.java new file mode 100644 index 00000000000..65a770743e8 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApi.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.v1; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import org.eclipse.edc.api.model.ApiCoreSchema; +import org.eclipse.edc.connector.api.management.configuration.ManagementApiSchema; +import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; + +@OpenAPIDefinition +@Tag(name = "EDR Cache") +public interface EdrCacheApi { + + @Operation(description = "Request all Edr entries according to a particular query", + requestBody = @RequestBody( + content = @Content(schema = @Schema(implementation = ApiCoreSchema.QuerySpecSchema.class)) + ), + responses = { + @ApiResponse(responseCode = "200", description = "The edr entries matching the query", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = EndpointDataReferenceEntrySchema.class)))), + @ApiResponse(responseCode = "400", description = "Request body was malformed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + }) + JsonArray requestEdrEntries(JsonObject querySpecJson); + + @Operation(description = "Gets the EDR data address with the given transfer process ID", + responses = { + @ApiResponse(responseCode = "200", description = "The data address", + content = @Content(schema = @Schema(implementation = ManagementApiSchema.DataAddressSchema.class))), + @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))), + @ApiResponse(responseCode = "404", description = "An EDR data address with the given transfer process ID does not exist", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + } + ) + JsonObject getEdrEntryDataAddress(String transferProcessId); + + @Operation(description = "Removes an EDR entry given the transfer process ID", + responses = { + @ApiResponse(responseCode = "204", description = "EDR entry was deleted successfully"), + @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))), + @ApiResponse(responseCode = "404", description = "An EDR entry with the given ID does not exist", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + }) + void removeEdrEntry(String transferProcessId); + + + @ArraySchema() + @Schema(name = "EndpointDataReferenceEntry", example = EndpointDataReferenceEntrySchema.EDR_ENTRY_OUTPUT_EXAMPLE) + record EndpointDataReferenceEntrySchema( + @Schema(name = ID) + String id, + @Schema(name = TYPE, example = EndpointDataReferenceEntry.EDR_ENTRY_TYPE) + String type + ) { + public static final String EDR_ENTRY_OUTPUT_EXAMPLE = """ + { + "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, + "@id": "transfer-process-id", + "transferProcessId": "transfer-process-id", + "agreementId": "agreement-id", + "contractNegotiationId": "contract-negotiation-id", + "assetId": "asset-id", + "providerId": "provider-id", + "createdAt": 1688465655 + } + """; + } + +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiController.java b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiController.java new file mode 100644 index 00000000000..e7f79498e68 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/main/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.v1; + + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; +import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.spi.exception.InvalidRequestException; +import org.eclipse.edc.web.spi.exception.ValidationFailureException; + +import static jakarta.json.stream.JsonCollectors.toJsonArray; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE; +import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v1/edrs") +public class EdrCacheApiController implements EdrCacheApi { + + private final EndpointDataReferenceStore edrStore; + + private final TypeTransformerRegistry transformerRegistry; + + private final JsonObjectValidatorRegistry validator; + + private final Monitor monitor; + + public EdrCacheApiController(EndpointDataReferenceStore edrStore, TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validator, Monitor monitor) { + this.edrStore = edrStore; + this.transformerRegistry = transformerRegistry; + this.validator = validator; + this.monitor = monitor; + } + + @POST + @Path("/request") + @Override + public JsonArray requestEdrEntries(JsonObject querySpecJson) { + QuerySpec querySpec; + if (querySpecJson == null) { + querySpec = QuerySpec.Builder.newInstance().build(); + } else { + validator.validate(EDC_QUERY_SPEC_TYPE, querySpecJson).orElseThrow(ValidationFailureException::new); + + querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class) + .orElseThrow(InvalidRequestException::new); + } + + return edrStore.query(querySpec) + .flatMap(ServiceResult::from) + .orElseThrow(exceptionMapper(QuerySpec.class, null)).stream() + .map(it -> transformerRegistry.transform(it, JsonObject.class)) + .peek(r -> r.onFailure(f -> monitor.warning(f.getFailureDetail()))) + .filter(Result::succeeded) + .map(Result::getContent) + .collect(toJsonArray()); + } + + @GET + @Path("{transferProcessId}/dataaddress") + @Override + public JsonObject getEdrEntryDataAddress(@PathParam("transferProcessId") String transferProcessId) { + var dataAddress = edrStore.resolveByTransferProcess(transferProcessId) + .flatMap(ServiceResult::from) + .orElseThrow(exceptionMapper(EndpointDataReferenceEntry.class, transferProcessId)); + + return transformerRegistry.transform(dataAddress, JsonObject.class) + .orElseThrow(f -> new EdcException(f.getFailureDetail())); + + + } + + @DELETE + @Path("{transferProcessId}") + @Override + public void removeEdrEntry(@PathParam("transferProcessId") String transferProcessId) { + edrStore.delete(transferProcessId) + .flatMap(ServiceResult::from) + .orElseThrow(exceptionMapper(EndpointDataReferenceEntry.class, transferProcessId)); + } + +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/edr-cache-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..64f5fe946fe --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.edc.connector.api.management.edr.EdrCacheApiExtension diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java new file mode 100644 index 00000000000..b328f8ad088 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.transform; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CONTRACT_NEGOTIATION_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CREATED_AT; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_PROVIDER_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TRANSFER_PROCESS_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class JsonObjectFromEndpointDataReferenceEntryTransformerTest { + + private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of()); + + private final TransformerContext context = mock(TransformerContext.class); + + private JsonObjectFromEndpointDataReferenceEntryTransformer transformer = new JsonObjectFromEndpointDataReferenceEntryTransformer(jsonFactory); + + @Test + void transform() { + + var entry = EndpointDataReferenceEntry.Builder.newInstance() + .transferProcessId("transferProcessId") + .assetId("assetId") + .providerId("providerId") + .agreementId("agreementId") + .contractNegotiationId("contractNegotiationId") + .build(); + + var result = transformer.transform(entry, context); + + assertThat(result).isNotNull(); + assertThat(result.getString(ID)).isEqualTo("transferProcessId"); + assertThat(result.getString(TYPE)).isEqualTo(EDR_ENTRY_TYPE); + assertThat(result.getString(EDR_ENTRY_CONTRACT_NEGOTIATION_ID)).isEqualTo("contractNegotiationId"); + assertThat(result.getString(EDR_ENTRY_TRANSFER_PROCESS_ID)).isEqualTo("transferProcessId"); + assertThat(result.getString(EDR_ENTRY_ASSET_ID)).isEqualTo("assetId"); + assertThat(result.getString(EDR_ENTRY_PROVIDER_ID)).isEqualTo("providerId"); + assertThat(result.getString(EDR_ENTRY_AGREEMENT_ID)).isEqualTo("agreementId"); + assertThat(result.getJsonNumber(EDR_ENTRY_CREATED_AT)).isNotNull(); + verify(context, never()).reportProblem(anyString()); + } +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiControllerTest.java b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiControllerTest.java new file mode 100644 index 00000000000..055874220ae --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiControllerTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.v1; + +import io.restassured.specification.RequestSpecification; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; +import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static jakarta.json.Json.createObjectBuilder; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CONTRACT_NEGOTIATION_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_PROVIDER_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TRANSFER_PROCESS_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ApiTest +public class EdrCacheApiControllerTest extends RestControllerTestBase { + + private static final String TEST_TRANSFER_PROCESS_ID = "test-transfer-process-id"; + private static final String TEST_TRANSFER_NEGOTIATION_ID = "test-cn-id"; + private static final String TEST_AGREEMENT_ID = "test-agreement-id"; + private static final String TEST_PROVIDER_ID = "test-provider-id"; + private static final String TEST_ASSET_ID = "test-asset-id"; + + private final TypeTransformerRegistry transformerRegistry = mock(); + private final JsonObjectValidatorRegistry validator = mock(); + private final EndpointDataReferenceStore edrStore = mock(); + + @Test + void requestEdrEntries() { + when(edrStore.query(any())) + .thenReturn(StoreResult.success(List.of(createEdrEntry()))); + when(transformerRegistry.transform(isA(EndpointDataReferenceEntry.class), eq(JsonObject.class))) + .thenReturn(Result.success(createEdrEntryJson().build())); + when(transformerRegistry.transform(isA(JsonObject.class), eq(QuerySpec.class))) + .thenReturn(Result.success(QuerySpec.Builder.newInstance().offset(10).build())); + when(validator.validate(any(), any())).thenReturn(ValidationResult.success()); + + baseRequest() + .contentType(JSON) + .body("{}") + .post("/edrs/request") + .then() + .log().ifError() + .statusCode(200) + .contentType(JSON) + .body("size()", is(1)); + + verify(edrStore).query(argThat(s -> s.getOffset() == 10)); + verify(transformerRegistry).transform(isA(EndpointDataReferenceEntry.class), eq(JsonObject.class)); + verify(transformerRegistry).transform(isA(JsonObject.class), eq(QuerySpec.class)); + } + + + @Test + void getEdrEntryDataAddress() { + + var dataAddressType = "type"; + var dataAddress = DataAddress.Builder.newInstance().type(dataAddressType).build(); + when(edrStore.resolveByTransferProcess("transferProcessId")) + .thenReturn(StoreResult.success(dataAddress)); + + when(transformerRegistry.transform(isA(DataAddress.class), eq(JsonObject.class))) + .thenReturn(Result.success(createDataAddress(dataAddressType).build())); + + baseRequest() + .contentType(JSON) + .get("/edrs/transferProcessId/dataaddress") + .then() + .log().ifError() + .statusCode(200) + .contentType(JSON) + .body("'%s'".formatted(DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY), equalTo(dataAddressType)); + + verify(edrStore).resolveByTransferProcess("transferProcessId"); + verify(transformerRegistry).transform(isA(DataAddress.class), eq(JsonObject.class)); + verifyNoMoreInteractions(transformerRegistry); + } + + @Test + void getEdrEntryDataAddress_whenNotFound() { + + when(edrStore.resolveByTransferProcess("transferProcessId")) + .thenReturn(StoreResult.notFound("notFound")); + + + baseRequest() + .contentType(JSON) + .get("/edrs/transferProcessId/dataaddress") + .then() + .log().ifError() + .statusCode(404) + .contentType(JSON); + + verify(edrStore).resolveByTransferProcess("transferProcessId"); + verifyNoMoreInteractions(transformerRegistry); + } + + @Test + void removeEdrEntry() { + when(edrStore.delete("transferProcessId")) + .thenReturn(StoreResult.success(createEdrEntry())); + + baseRequest() + .contentType(JSON) + .delete("/edrs/transferProcessId") + .then() + .statusCode(204); + verify(edrStore).delete("transferProcessId"); + } + + @Test + void removeEdrEntry_whenNotFound() { + when(edrStore.delete("transferProcessId")) + .thenReturn(StoreResult.notFound("not found")); + + baseRequest() + .contentType(JSON) + .delete("/edrs/transferProcessId") + .then() + .statusCode(404); + + verify(edrStore).delete("transferProcessId"); + } + + @Override + protected Object controller() { + return new EdrCacheApiController(edrStore, transformerRegistry, validator, mock()); + } + + private JsonObjectBuilder createEdrEntryJson() { + return createObjectBuilder() + .add(CONTEXT, createContextBuilder().build()) + .add(TYPE, EDR_ENTRY_TYPE) + .add(ID, TEST_TRANSFER_PROCESS_ID) + .add(EDR_ENTRY_TRANSFER_PROCESS_ID, TEST_TRANSFER_PROCESS_ID) + .add(EDR_ENTRY_PROVIDER_ID, TEST_PROVIDER_ID) + .add(EDR_ENTRY_CONTRACT_NEGOTIATION_ID, TEST_TRANSFER_NEGOTIATION_ID) + .add(EDR_ENTRY_ASSET_ID, TEST_ASSET_ID) + .add(EDR_ENTRY_AGREEMENT_ID, TEST_AGREEMENT_ID); + } + + private JsonObjectBuilder createDataAddress(String type) { + return createObjectBuilder() + .add(CONTEXT, createContextBuilder().build()) + .add(TYPE, DataAddress.EDC_DATA_ADDRESS_TYPE) + .add(DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY, type); + } + + private EndpointDataReferenceEntry createEdrEntry() { + return EndpointDataReferenceEntry.Builder.newInstance() + .agreementId(TEST_AGREEMENT_ID) + .assetId(TEST_ASSET_ID) + .providerId(TEST_PROVIDER_ID) + .transferProcessId(TEST_TRANSFER_PROCESS_ID) + .contractNegotiationId(TEST_TRANSFER_NEGOTIATION_ID) + .build(); + + } + + private JsonObjectBuilder createContextBuilder() { + return createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .add(EDC_PREFIX, EDC_NAMESPACE); + } + + + private RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port + "/v1") + .when(); + } +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiExtensionTest.java b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiExtensionTest.java new file mode 100644 index 00000000000..d1008119416 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiExtensionTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.v1; + +import org.eclipse.edc.connector.api.management.edr.EdrCacheApiExtension; +import org.eclipse.edc.connector.api.management.edr.transform.JsonObjectFromEndpointDataReferenceEntryTransformer; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class EdrCacheApiExtensionTest { + + private final JsonObjectValidatorRegistry validatorRegistry = mock(); + private final WebService webService = mock(); + + private final TypeTransformerRegistry transformerRegistry = mock(); + + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); + context.registerService(WebService.class, webService); + context.registerService(TypeTransformerRegistry.class, transformerRegistry); + when(transformerRegistry.forContext("management-api")).thenReturn(transformerRegistry); + } + + @Test + void initialize_shouldRegisterControllers(EdrCacheApiExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(webService).registerResource(any(), isA(EdrCacheApiController.class)); + } + + @Test + void initialize_shouldRegisterTransformers(EdrCacheApiExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + verify(transformerRegistry).register(isA(JsonObjectFromEndpointDataReferenceEntryTransformer.class)); + } +} diff --git a/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiTest.java b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiTest.java new file mode 100644 index 00000000000..c4deef55a99 --- /dev/null +++ b/extensions/control-plane/api/management-api/edr-cache-api/src/test/java/org/eclipse/edc/connector/api/management/edr/v1/EdrCacheApiTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.edr.v1; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.api.management.edr.v1.EdrCacheApi.EndpointDataReferenceEntrySchema.EDR_ENTRY_OUTPUT_EXAMPLE; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CONTRACT_NEGOTIATION_ID; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_CREATED_AT; +import static org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_PROVIDER_ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.Mockito.mock; + +public class EdrCacheApiTest { + + private final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper(); + private final JsonLd jsonLd = new TitaniumJsonLd(mock()); + + @Test + void edrEntryOutputExample() throws JsonProcessingException { + var jsonObject = objectMapper.readValue(EDR_ENTRY_OUTPUT_EXAMPLE, JsonObject.class); + var expanded = jsonLd.expand(jsonObject); + + assertThat(expanded).isSucceeded().satisfies(content -> { + assertThat(content.getString(ID)).isNotBlank(); + assertThat(content.getJsonArray(EDR_ENTRY_AGREEMENT_ID).getJsonObject(0).getString(VALUE)).isNotBlank(); + assertThat(content.getJsonArray(EDR_ENTRY_CONTRACT_NEGOTIATION_ID).getJsonObject(0).getString(VALUE)).isNotBlank(); + assertThat(content.getJsonArray(EDR_ENTRY_PROVIDER_ID).getJsonObject(0).getString(VALUE)).isNotBlank(); + assertThat(content.getJsonArray(EDR_ENTRY_ASSET_ID).getJsonObject(0).getString(VALUE)).isNotBlank(); + assertThat(content.getJsonArray(EDR_ENTRY_CREATED_AT).getJsonObject(0).getJsonNumber(VALUE)).isNotNull(); + }); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c8752a4ec1e..92419fc3ae2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -154,6 +154,7 @@ include(":extensions:control-plane:api:management-api:contract-negotiation-api") include(":extensions:control-plane:api:management-api:management-api-test-fixtures") include(":extensions:control-plane:api:management-api:policy-definition-api") include(":extensions:control-plane:api:management-api:transfer-process-api") +include(":extensions:control-plane:api:management-api:edr-cache-api") include(":extensions:control-plane:transfer:transfer-data-plane") include(":extensions:control-plane:transfer:transfer-data-plane-signaling") include(":extensions:control-plane:transfer:transfer-pull-http-receiver") diff --git a/spi/common/edr-store-spi/src/main/java/org/eclipse/edc/edr/spi/types/EndpointDataReferenceEntry.java b/spi/common/edr-store-spi/src/main/java/org/eclipse/edc/edr/spi/types/EndpointDataReferenceEntry.java index 6baf7babc3a..0dd02ff7914 100644 --- a/spi/common/edr-store-spi/src/main/java/org/eclipse/edc/edr/spi/types/EndpointDataReferenceEntry.java +++ b/spi/common/edr-store-spi/src/main/java/org/eclipse/edc/edr/spi/types/EndpointDataReferenceEntry.java @@ -15,6 +15,7 @@ package org.eclipse.edc.edr.spi.types; import com.fasterxml.jackson.annotation.JsonCreator; +import org.eclipse.edc.spi.entity.Entity; import static java.util.Objects.requireNonNull; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; @@ -22,7 +23,7 @@ /** * Represents metadata associated with an EDR */ -public class EndpointDataReferenceEntry { +public class EndpointDataReferenceEntry extends Entity { public static final String SIMPLE_TYPE = "EndpointDataReferenceEntry"; public static final String EDR_ENTRY_TYPE = EDC_NAMESPACE + SIMPLE_TYPE; @@ -38,6 +39,9 @@ public class EndpointDataReferenceEntry { public static final String EDR_ENTRY_TRANSFER_PROCESS_ID = EDC_NAMESPACE + TRANSFER_PROCESS_ID; public static final String PROVIDER_ID = "providerId"; public static final String EDR_ENTRY_PROVIDER_ID = EDC_NAMESPACE + PROVIDER_ID; + + public static final String CREATED_AT = "createdAt"; + public static final String EDR_ENTRY_CREATED_AT = EDC_NAMESPACE + CREATED_AT; private String assetId; private String agreementId; private String transferProcessId; @@ -68,12 +72,10 @@ public String getAssetId() { return assetId; } - public static class Builder { - - private final EndpointDataReferenceEntry entry; + public static class Builder extends Entity.Builder { private Builder() { - entry = new EndpointDataReferenceEntry(); + super(new EndpointDataReferenceEntry()); } @JsonCreator @@ -82,37 +84,45 @@ public static Builder newInstance() { } public Builder assetId(String assetId) { - entry.assetId = assetId; + entity.assetId = assetId; return this; } public Builder agreementId(String agreementId) { - entry.agreementId = agreementId; + entity.agreementId = agreementId; return this; } public Builder transferProcessId(String transferProcessId) { - entry.transferProcessId = transferProcessId; + entity.transferProcessId = transferProcessId; return this; } public Builder providerId(String providerId) { - entry.providerId = providerId; + entity.providerId = providerId; return this; } public Builder contractNegotiationId(String contractNegotiationId) { - entry.contractNegotiationId = contractNegotiationId; + entity.contractNegotiationId = contractNegotiationId; + return this; + } + + @Override + public Builder self() { return this; } public EndpointDataReferenceEntry build() { - requireNonNull(entry.assetId, ASSET_ID); - requireNonNull(entry.agreementId, AGREEMENT_ID); - requireNonNull(entry.transferProcessId, TRANSFER_PROCESS_ID); - requireNonNull(entry.contractNegotiationId, TRANSFER_PROCESS_ID); - requireNonNull(entry.providerId, TRANSFER_PROCESS_ID); - return entry; + super.build(); + requireNonNull(entity.assetId, ASSET_ID); + requireNonNull(entity.agreementId, AGREEMENT_ID); + requireNonNull(entity.transferProcessId, TRANSFER_PROCESS_ID); + requireNonNull(entity.contractNegotiationId, TRANSFER_PROCESS_ID); + requireNonNull(entity.providerId, TRANSFER_PROCESS_ID); + // The id is always equals to transfer process id + entity.id = entity.transferProcessId; + return entity; } } }