Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
implementation(project(":extensions:agreements:retirement-evaluation-spi"))
implementation(libs.edc.json.ld.spi)
implementation(libs.edc.web.spi)
implementation(libs.edc.api.lib)
implementation(libs.edc.management.api.lib)
implementation(libs.edc.jersey.providers.lib)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package eu.dataspace.connector.agreements.retirement.api;

import eu.dataspace.connector.agreements.retirement.api.transform.JsonObjectFromAgreementRetirementTransformer;
import eu.dataspace.connector.agreements.retirement.api.transform.JsonObjectFromContractAgreementEnrichedTransformer;
import eu.dataspace.connector.agreements.retirement.api.transform.JsonObjectToAgreementsRetirementEntryTransformer;
import eu.dataspace.connector.agreements.retirement.api.v3.AgreementsRetirementApiV3Controller;
import eu.dataspace.connector.agreements.retirement.api.v3.EnhancedContractAgreementApiV3Controller;
import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.service.EnhancedAgreementService;
import jakarta.json.Json;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
Expand Down Expand Up @@ -44,6 +47,8 @@ public String name() {
@Inject
private AgreementsRetirementService agreementsRetirementService;
@Inject
private EnhancedAgreementService enhancedAgreementService;
@Inject
private Monitor monitor;
@Inject
private JsonLd jsonLd;
Expand All @@ -56,11 +61,15 @@ public void initialize(ServiceExtensionContext context) {
var managementTypeTransformerRegistry = transformerRegistry.forContext("management-api");

managementTypeTransformerRegistry.register(new JsonObjectFromAgreementRetirementTransformer(jsonFactory));
managementTypeTransformerRegistry.register(new JsonObjectFromContractAgreementEnrichedTransformer(jsonFactory));
managementTypeTransformerRegistry.register(new JsonObjectToAgreementsRetirementEntryTransformer(monitor));

webService.registerResource(ApiContext.MANAGEMENT, new AgreementsRetirementApiV3Controller(agreementsRetirementService, managementTypeTransformerRegistry, validator, monitor));
webService.registerResource(ApiContext.MANAGEMENT, new EnhancedContractAgreementApiV3Controller(enhancedAgreementService, managementTypeTransformerRegistry, validator, monitor));

var jsonLdInterceptor = new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE);
webService.registerDynamicResource(ApiContext.MANAGEMENT, AgreementsRetirementApiV3Controller.class, jsonLdInterceptor);
webService.registerDynamicResource(ApiContext.MANAGEMENT, EnhancedContractAgreementApiV3Controller.class, jsonLdInterceptor);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package eu.dataspace.connector.agreements.retirement.api.transform;

import eu.dataspace.connector.agreements.retirement.spi.types.EnhancedContractAgreement;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
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.spi.constants.CoreConstants.EDC_NAMESPACE;

public class JsonObjectFromContractAgreementEnrichedTransformer extends AbstractJsonLdTransformer<EnhancedContractAgreement, JsonObject> {

private final JsonBuilderFactory jsonFactory;

public JsonObjectFromContractAgreementEnrichedTransformer(JsonBuilderFactory jsonFactory) {
super(EnhancedContractAgreement.class, JsonObject.class);
this.jsonFactory = jsonFactory;
}

@Override
public @Nullable JsonObject transform(@NotNull EnhancedContractAgreement entry, @NotNull TransformerContext transformerContext) {
var agreement = transformerContext.transform(entry.agreement(), JsonObject.class);
var builder = jsonFactory.createObjectBuilder(agreement);
if (entry.retirement() != null) {
builder.add(EDC_NAMESPACE + "isRetired", true);
builder.add(EDC_NAMESPACE + "retiredAt", entry.retirement().getAgreementRetirementDate());
builder.add(EDC_NAMESPACE + "retirementReason", entry.retirement().getReason());
} else {
builder.add(EDC_NAMESPACE + "isRetired", false);
}

return builder.build();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2026 Mobility Data Space
*
* 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:
* Think-it GmbH - initial API and implementation
*/

package eu.dataspace.connector.agreements.retirement.api.v3;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.info.Info;
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.management.schema.ManagementApiSchema;
import org.eclipse.edc.api.model.ApiCoreSchema;
import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement;
import org.eclipse.edc.jsonld.spi.JsonLdKeywords;

import static eu.dataspace.connector.agreements.retirement.api.v3.EnhancedContractAgreementApiV3.EnhancedContractAgreementSchema.ENHANCED_CONTRACT_AGREEMENT_EXAMPLE;

@OpenAPIDefinition(info = @Info(version = "v3"))
@Tag(name = "Enhanced Contract Agreement V3")
public interface EnhancedContractAgreementApiV3 {

@Operation(description = "Gets all enhanced contract agreements according to a particular query",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = ApiCoreSchema.QuerySpecSchema.class))),
responses = {
@ApiResponse(responseCode = "200", description = "The contract agreements matching the query",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ManagementApiSchema.ContractAgreementSchema.class)))),
@ApiResponse(responseCode = "400", description = "Request body was malformed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))))
}
)
JsonArray getAllEnhancedAgreements(JsonObject querySpecJson);

@Schema(name = "EnhancedContractAgreement", example = ENHANCED_CONTRACT_AGREEMENT_EXAMPLE)
record EnhancedContractAgreementSchema(
@Schema(name = JsonLdKeywords.TYPE, example = ContractAgreement.CONTRACT_AGREEMENT_TYPE)
String ldType,
@Schema(name = JsonLdKeywords.ID)
String id,
String providerId,
String consumerId,
long contractSigningDate,
String assetId,
ManagementApiSchema.PolicySchema policy,
boolean isRetired,
long retiredAt,
String retiredReason
) {
public static final String ENHANCED_CONTRACT_AGREEMENT_EXAMPLE = """
{
"@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" },
"@type": "https://w3id.org/edc/v0.0.1/ns/ContractAgreement",
"@id": "negotiation-id",
"providerId": "provider-id",
"consumerId": "consumer-id",
"assetId": "asset-id",
"contractSigningDate": 1688465655,
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Set",
"@id": "offer-id",
"permission": [{
"target": "asset-id",
"action": "display"
}]
},
"isRetired": true,
"retiredAt": 1788465655,
"retiredReason": "a good reason"
}
""";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package eu.dataspace.connector.agreements.retirement.api.v3;

import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.service.EnhancedAgreementService;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
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.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("/v3/contractagreements")
public class EnhancedContractAgreementApiV3Controller implements EnhancedContractAgreementApiV3 {

private final EnhancedAgreementService service;
private final TypeTransformerRegistry transformerRegistry;
private final JsonObjectValidatorRegistry validator;
private final Monitor monitor;


public EnhancedContractAgreementApiV3Controller(EnhancedAgreementService service, TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validator, Monitor monitor) {
this.service = service;
this.transformerRegistry = transformerRegistry;
this.validator = validator;
this.monitor = monitor;
}

@POST
@Path("/request-enhanced")
public JsonArray getAllEnhancedAgreements(@RequestBody JsonObject querySpecJson) {

QuerySpec querySpec;
if (querySpecJson == null) {
querySpec = QuerySpec.max();
} else {
validator.validate(EDC_QUERY_SPEC_TYPE, querySpecJson).orElseThrow(ValidationFailureException::new);

querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class)
.orElseThrow(InvalidRequestException::new);
}

return service.findAllAgreements(querySpec)
.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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
testImplementation(libs.assertj)
testImplementation(libs.mockito.core)
testImplementation(libs.edc.junit)
testImplementation(libs.edc.control.plane.core)
testImplementation(libs.junit.jupiter)
testImplementation(libs.junit.platform.launcher)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package eu.dataspace.connector.agreements.retirement.defaults;

import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
Expand All @@ -21,11 +22,13 @@ public String name() {
}

@Inject
CriterionOperatorRegistry criterionOperatorRegistry;
private CriterionOperatorRegistry criterionOperatorRegistry;
@Inject
private ContractNegotiationStore contractNegotiationStore;

@Provider(isDefault = true)
public AgreementsRetirementStore createInMemStore() {
return new InMemoryAgreementsRetirementStore(criterionOperatorRegistry);
return new InMemoryAgreementsRetirementStore(criterionOperatorRegistry, contractNegotiationStore);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package eu.dataspace.connector.agreements.retirement.defaults;

import eu.dataspace.connector.agreements.retirement.spi.types.EnhancedContractAgreement;
import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.spi.query.CriterionOperatorRegistry;
import org.eclipse.edc.spi.query.QueryResolver;
import org.eclipse.edc.spi.query.QuerySpec;
Expand All @@ -18,10 +20,12 @@
public class InMemoryAgreementsRetirementStore implements AgreementsRetirementStore {

private final QueryResolver<AgreementsRetirementEntry> queryResolver;
private final ContractNegotiationStore contractNegotiationStore;
private final Map<String, AgreementsRetirementEntry> cache = new ConcurrentHashMap<>();

public InMemoryAgreementsRetirementStore(CriterionOperatorRegistry criterionOperatorRegistry) {
public InMemoryAgreementsRetirementStore(CriterionOperatorRegistry criterionOperatorRegistry, ContractNegotiationStore contractNegotiationStore) {
queryResolver = new ReflectionBasedQueryResolver<>(AgreementsRetirementEntry.class, criterionOperatorRegistry);
this.contractNegotiationStore = contractNegotiationStore;
}

@Override
Expand All @@ -44,4 +48,10 @@ public StoreResult<Void> delete(String contractAgreementId) {
public Stream<AgreementsRetirementEntry> findRetiredAgreements(QuerySpec querySpec) {
return queryResolver.query(cache.values().stream(), querySpec);
}

@Override
public Stream<EnhancedContractAgreement> findEnhancedAgreements(QuerySpec querySpec) {
return contractNegotiationStore.queryAgreements(querySpec)
.map(agreement -> new EnhancedContractAgreement(agreement, cache.get(agreement.getId())));
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package eu.dataspace.connector.agreements.retirement.service;

import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.service.EnhancedAgreementService;
import eu.dataspace.connector.agreements.retirement.spi.store.AgreementsRetirementStore;
import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.event.EventRouter;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.transaction.spi.TransactionContext;
import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.store.AgreementsRetirementStore;

import java.time.Clock;

Expand All @@ -35,8 +36,13 @@ public String name() {
return NAME;
}

@Provider()
public AgreementsRetirementService createInMemAgreementRetirementService() {
@Provider
public AgreementsRetirementService agreementsRetirementService() {
return new AgreementsRetirementServiceImpl(store, transactionContext, contractAgreementService, eventRouter, clock);
}

@Provider
public EnhancedAgreementService enhancedAgreementService() {
return new EnhancedAgreementServiceImpl(store, transactionContext);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package eu.dataspace.connector.agreements.retirement.service;

import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementEvent;
import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementReactivated;
import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementRetired;
import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.store.AgreementsRetirementStore;
import eu.dataspace.connector.agreements.retirement.spi.types.AgreementsRetirementEntry;
import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService;
import org.eclipse.edc.spi.event.EventEnvelope;
import org.eclipse.edc.spi.event.EventRouter;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.transaction.spi.TransactionContext;
import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementEvent;
import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementReactivated;
import eu.dataspace.connector.agreements.retirement.spi.event.ContractAgreementRetired;
import eu.dataspace.connector.agreements.retirement.spi.service.AgreementsRetirementService;
import eu.dataspace.connector.agreements.retirement.spi.store.AgreementsRetirementStore;
import eu.dataspace.connector.agreements.retirement.spi.types.AgreementsRetirementEntry;

import java.time.Clock;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package eu.dataspace.connector.agreements.retirement.service;

import eu.dataspace.connector.agreements.retirement.spi.service.EnhancedAgreementService;
import eu.dataspace.connector.agreements.retirement.spi.store.AgreementsRetirementStore;
import eu.dataspace.connector.agreements.retirement.spi.types.EnhancedContractAgreement;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.util.List;
import java.util.stream.Collectors;

public class EnhancedAgreementServiceImpl implements EnhancedAgreementService {

private final AgreementsRetirementStore store;
private final TransactionContext transactionContext;

public EnhancedAgreementServiceImpl(AgreementsRetirementStore store, TransactionContext transactionContext) {
this.store = store;
this.transactionContext = transactionContext;
}

@Override
public ServiceResult<List<EnhancedContractAgreement>> findAllAgreements(QuerySpec querySpec) {
return transactionContext.execute(() -> ServiceResult.success(store.findEnhancedAgreements(querySpec).collect(Collectors.toList())));
}

}
Loading