Skip to content

Commit

Permalink
feat: EDR cache API (#3983)
Browse files Browse the repository at this point in the history
* feat: EDR cache API for query/fetching/deleting cached EDR

* pr remarks
  • Loading branch information
wolf4ood committed Mar 11, 2024
1 parent c7e0e13 commit 99ecae9
Show file tree
Hide file tree
Showing 12 changed files with 833 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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")
}
}


Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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<EndpointDataReferenceEntry, JsonObject> {
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();
}
}
Original file line number Diff line number Diff line change
@@ -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
}
""";
}

}
Original file line number Diff line number Diff line change
@@ -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));
}

}
Loading

0 comments on commit 99ecae9

Please sign in to comment.