From 8c49531cf645adc59dcba4d3adece55576114c5d Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Wed, 27 Nov 2024 09:48:38 +0100 Subject: [PATCH] fix: assignee and assigner serialization as @id (#4641) * fix: assignee and assigner serialization as `@id` * chore: switched to v4alpha --- .../TypeTransformerRegistryImpl.java | 34 +++------ .../TypeTransformerRegistryImplTest.java | 29 +++++++- .../from/JsonObjectFromPolicyTransformer.java | 51 ++++++++----- .../JsonObjectFromPolicyTransformerTest.java | 41 +++++++---- .../DspApiConfigurationExtension.java | 11 ++- .../edc/api/management/ManagementApi.java | 22 ++++++ .../ManagementApiConfigurationExtension.java | 13 ++-- .../resources/management-api-version.json | 6 ++ ...nagementApiConfigurationExtensionTest.java | 6 +- .../ContractAgreementApiExtension.java | 9 ++- .../v4alpha/ContractAgreementApiV4Alpha.java | 71 +++++++++++++++++++ ...ContractAgreementApiV4AlphaController.java | 61 ++++++++++++++++ .../ContractAgreementApiExtensionTest.java | 7 ++ ...ractAgreementApiV4AlphaControllerTest.java | 34 +++++++++ .../ContractAgreementApiEndToEndTest.java | 39 +++++++--- .../versionapi/VersionApiEndToEndTest.java | 5 +- 16 files changed, 360 insertions(+), 79 deletions(-) create mode 100644 extensions/common/api/lib/management-api-lib/src/main/java/org/eclipse/edc/api/management/ManagementApi.java create mode 100644 extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4Alpha.java create mode 100644 extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4AlphaController.java create mode 100644 extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v31alpha/ContractAgreementApiV4AlphaControllerTest.java diff --git a/core/common/lib/transform-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java b/core/common/lib/transform-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java index 0b21fc82693..fc46f53b633 100644 --- a/core/common/lib/transform-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java +++ b/core/common/lib/transform-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import static java.lang.String.format; @@ -32,6 +33,14 @@ public class TypeTransformerRegistryImpl implements TypeTransformerRegistry { private final Map> aliases = new HashMap<>(); private final List> transformers = new ArrayList<>(); private final Map contextRegistries = new HashMap<>(); + private TypeTransformerRegistry parent; + + public TypeTransformerRegistryImpl() { + } + + private TypeTransformerRegistryImpl(TypeTransformerRegistry parent) { + this.parent = parent; + } @Override public void register(TypeTransformer transformer) { @@ -40,7 +49,7 @@ public void register(TypeTransformer transformer) { @Override public @NotNull TypeTransformerRegistry forContext(String context) { - return contextRegistries.computeIfAbsent(context, k -> new ContextTransformerRegistry(this)); + return contextRegistries.computeIfAbsent(context, k -> new TypeTransformerRegistryImpl(this)); } @Override @@ -49,6 +58,7 @@ public void register(TypeTransformer transformer) { .filter(t -> t.getInputType().isInstance(input) && t.getOutputType().equals(outputType)) .findAny() .map(it -> (TypeTransformer) it) + .or(() -> Optional.ofNullable(parent).map(p -> p.transformerFor(input, outputType))) .orElseThrow(() -> new EdcException(format("No Transformer registered that can handle %s -> %s", input.getClass(), outputType))); } @@ -66,26 +76,4 @@ public Result transform(@NotNull INPUT input, @NotNull C } } - private static class ContextTransformerRegistry extends TypeTransformerRegistryImpl { - - private final TypeTransformerRegistry parent; - - ContextTransformerRegistry(TypeTransformerRegistry parent) { - this.parent = parent; - } - - @Override - public @NotNull TypeTransformerRegistry forContext(String context) { - throw new EdcException("'forContext' cannot be called on ContextTransformerRegistry, please refer to the generic TypeTransformerRegistry"); - } - - @Override - public @NotNull TypeTransformer transformerFor(@NotNull INPUT input, @NotNull Class outputType) { - try { - return super.transformerFor(input, outputType); - } catch (EdcException e) { - return parent.transformerFor(input, outputType); - } - } - } } diff --git a/core/common/lib/transform-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java b/core/common/lib/transform-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java index 8e375a64561..182ccf60925 100644 --- a/core/common/lib/transform-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java +++ b/core/common/lib/transform-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java @@ -15,6 +15,7 @@ package org.eclipse.edc.transform; import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.transform.spi.TypeTransformer; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -23,6 +24,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; public class TypeTransformerRegistryImplTest { @@ -75,9 +78,31 @@ void shouldReturnRegistryForSpecificContext() { } @Test - void shouldThrowException_whenForContextIsCalled() { - assertThatThrownBy(() -> contextRegistry.forContext("any")).isInstanceOf(EdcException.class); + void shouldReturnRegistryForSpecificNestedContext() { + var nestedContextRegistry = contextRegistry.forContext("nestedContext"); + nestedContextRegistry.register(new IntegerStringTypeTransformer()); + + assertThat(nestedContextRegistry.transform(5, String.class)) + .isSucceeded().isEqualTo("5"); + assertThatThrownBy(() -> contextRegistry.transform(5, String.class)).isInstanceOf(EdcException.class); + assertThatThrownBy(() -> registry.transform(5, String.class)).isInstanceOf(EdcException.class); } + + @Test + void shouldTransformUsingNestedContext() { + var nestedContextRegistry = contextRegistry.forContext("nestedContext"); + nestedContextRegistry.register(new IntegerStringTypeTransformer()); + + TypeTransformer typeTransformer = mock(); + contextRegistry.register(typeTransformer); + registry.register(typeTransformer); + + assertThat(nestedContextRegistry.transform(5, String.class)) + .isSucceeded().isEqualTo("5"); + + verifyNoInteractions(typeTransformer); + } + } @Nested diff --git a/core/control-plane/control-plane-transform/src/main/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformer.java b/core/control-plane/control-plane-transform/src/main/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformer.java index cd74316129f..91549bd7e5f 100644 --- a/core/control-plane/control-plane-transform/src/main/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformer.java +++ b/core/control-plane/control-plane-transform/src/main/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformer.java @@ -19,6 +19,7 @@ import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; import org.eclipse.edc.participant.spi.ParticipantIdMapper; import org.eclipse.edc.policy.model.Action; @@ -76,16 +77,22 @@ public class JsonObjectFromPolicyTransformer extends AbstractJsonLdTransformer { private final JsonBuilderFactory jsonFactory; private final ParticipantIdMapper participantIdMapper; + private final boolean participantsAsId; public JsonObjectFromPolicyTransformer(JsonBuilderFactory jsonFactory, ParticipantIdMapper participantIdMapper) { + this(jsonFactory, participantIdMapper, false); + } + + public JsonObjectFromPolicyTransformer(JsonBuilderFactory jsonFactory, ParticipantIdMapper participantIdMapper, boolean participantsAsId) { super(Policy.class, JsonObject.class); this.jsonFactory = jsonFactory; this.participantIdMapper = participantIdMapper; + this.participantsAsId = participantsAsId; } @Override public @Nullable JsonObject transform(@NotNull Policy policy, @NotNull TransformerContext context) { - return policy.accept(new Visitor(jsonFactory, participantIdMapper)); + return policy.accept(new Visitor(jsonFactory, participantIdMapper, participantsAsId)); } /** @@ -94,10 +101,12 @@ public JsonObjectFromPolicyTransformer(JsonBuilderFactory jsonFactory, Participa private static class Visitor implements Policy.Visitor, Rule.Visitor, Constraint.Visitor, Expression.Visitor { private final JsonBuilderFactory jsonFactory; private final ParticipantIdMapper participantIdMapper; + private final boolean participantsAsId; - Visitor(JsonBuilderFactory jsonFactory, ParticipantIdMapper participantIdMapper) { + Visitor(JsonBuilderFactory jsonFactory, ParticipantIdMapper participantIdMapper, boolean participantsAsId) { this.jsonFactory = jsonFactory; this.participantIdMapper = participantIdMapper; + this.participantsAsId = participantsAsId; } @Override @@ -115,19 +124,6 @@ public JsonObject visitXoneConstraint(XoneConstraint xoneConstraint) { return visitMultiplicityConstraint(ODRL_XONE_CONSTRAINT_ATTRIBUTE, xoneConstraint); } - private JsonObject visitMultiplicityConstraint(String operandType, MultiplicityConstraint multiplicityConstraint) { - var constraintsBuilder = jsonFactory.createArrayBuilder(); - for (var constraint : multiplicityConstraint.getConstraints()) { - Optional.of(constraint) - .map(c -> c.accept(this)) - .ifPresent(constraintsBuilder::add); - } - - return jsonFactory.createObjectBuilder() - .add(operandType, constraintsBuilder.build()) - .build(); - } - @Override public JsonObject visitAtomicConstraint(AtomicConstraint atomicConstraint) { var constraintBuilder = jsonFactory.createObjectBuilder(); @@ -166,8 +162,8 @@ public JsonObject visitPolicy(Policy policy) { .add(ODRL_PROHIBITION_ATTRIBUTE, prohibitionsBuilder) .add(ODRL_OBLIGATION_ATTRIBUTE, obligationsBuilder); - Optional.ofNullable(policy.getAssignee()).map(participantIdMapper::toIri).ifPresent(it -> builder.add(ODRL_ASSIGNEE_ATTRIBUTE, it)); - Optional.ofNullable(policy.getAssigner()).map(participantIdMapper::toIri).ifPresent(it -> builder.add(ODRL_ASSIGNER_ATTRIBUTE, it)); + Optional.ofNullable(policy.getAssignee()).map(participantIdMapper::toIri).ifPresent(it -> builder.add(ODRL_ASSIGNEE_ATTRIBUTE, visitParticipantId(it))); + Optional.ofNullable(policy.getAssigner()).map(participantIdMapper::toIri).ifPresent(it -> builder.add(ODRL_ASSIGNER_ATTRIBUTE, visitParticipantId(it))); Optional.ofNullable(policy.getTarget()) .ifPresent(target -> builder.add( @@ -219,6 +215,27 @@ public JsonObject visitDuty(Duty duty) { return obligationBuilder.build(); } + private JsonValue visitParticipantId(String participantId) { + if (participantsAsId) { + return jsonFactory.createObjectBuilder().add(ID, participantId).build(); + } else { + return Json.createValue(participantId); + } + } + + private JsonObject visitMultiplicityConstraint(String operandType, MultiplicityConstraint multiplicityConstraint) { + var constraintsBuilder = jsonFactory.createArrayBuilder(); + for (var constraint : multiplicityConstraint.getConstraints()) { + Optional.of(constraint) + .map(c -> c.accept(this)) + .ifPresent(constraintsBuilder::add); + } + + return jsonFactory.createObjectBuilder() + .add(operandType, constraintsBuilder.build()) + .build(); + } + private JsonObjectBuilder visitRule(Rule rule) { var ruleBuilder = jsonFactory.createObjectBuilder(); diff --git a/core/control-plane/control-plane-transform/src/test/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformerTest.java b/core/control-plane/control-plane-transform/src/test/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformerTest.java index 79b54dbf14d..f8b1ab71a15 100644 --- a/core/control-plane/control-plane-transform/src/test/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformerTest.java +++ b/core/control-plane/control-plane-transform/src/test/java/org/eclipse/edc/connector/controlplane/transform/odrl/from/JsonObjectFromPolicyTransformerTest.java @@ -372,6 +372,23 @@ void transform_xoneConstraint_returnJsonObject() { verify(context, never()).reportProblem(anyString()); } + @Test + void transform_assigneeAndAssignerAsIds() { + var transformer = new JsonObjectFromPolicyTransformer(jsonFactory, participantIdMapper, true); + var policy = Policy.Builder.newInstance() + .target("target") + .assignee("assignee") + .assigner("assigner") + .build(); + + var result = transformer.transform(policy, context); + + assertThat(result.getJsonObject(ODRL_ASSIGNEE_ATTRIBUTE).getString(ID)).isEqualTo("assignee"); + assertThat(result.getJsonObject(ODRL_ASSIGNER_ATTRIBUTE).getString(ID)).isEqualTo("assigner"); + + verify(context, never()).reportProblem(anyString()); + } + @ParameterizedTest @ArgumentsSource(PolicyTypeToOdrl.class) void shouldMapPolicyTypeToOdrlType(PolicyType type, String expectedType) { @@ -383,18 +400,6 @@ void shouldMapPolicyTypeToOdrlType(PolicyType type, String expectedType) { assertThat(result.getString(TYPE)).isEqualTo(expectedType); } - private static class PolicyTypeToOdrl implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext extensionContext) { - return Stream.of( - arguments(SET, ODRL_POLICY_TYPE_SET), - arguments(OFFER, ODRL_POLICY_TYPE_OFFER), - arguments(CONTRACT, ODRL_POLICY_TYPE_AGREEMENT) - ); - } - } - private Action getAction() { return Action.Builder.newInstance().type("use").build(); } @@ -406,4 +411,16 @@ private AtomicConstraint getConstraint() { .rightExpression(new LiteralExpression("right")) .build(); } + + private static class PolicyTypeToOdrl implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + arguments(SET, ODRL_POLICY_TYPE_SET), + arguments(OFFER, ODRL_POLICY_TYPE_OFFER), + arguments(CONTRACT, ODRL_POLICY_TYPE_AGREEMENT) + ); + } + } } diff --git a/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java b/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java index ec21c6722f2..95a4d3c32cf 100644 --- a/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java +++ b/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java @@ -81,13 +81,8 @@ public class DspApiConfigurationExtension implements ServiceExtension { public static final String NAME = "Dataspace Protocol API Configuration Extension"; - - @Setting(description = "Configures endpoint for reaching the Protocol API in the form \"\"", key = "edc.dsp.callback.address", required = false) - private String callbackAddress; - @SettingContext("Protocol API context setting key") private static final String PROTOCOL_CONFIG_KEY = "web.http." + ApiContext.PROTOCOL; - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() .apiConfigKey(PROTOCOL_CONFIG_KEY) .contextAlias(ApiContext.PROTOCOL) @@ -95,7 +90,8 @@ public class DspApiConfigurationExtension implements ServiceExtension { .defaultPort(8282) .name("Protocol API") .build(); - + @Setting(description = "Configures endpoint for reaching the Protocol API in the form \"\"", key = "edc.dsp.callback.address", required = false) + private String callbackAddress; @Inject private TypeManager typeManager; @Inject @@ -158,7 +154,6 @@ private void registerTransformers(String version, JsonLdNamespace dspNamespace, // EDC model to JSON-LD transformers var dspApiTransformerRegistry = transformerRegistry.forContext(version); - dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper)); dspApiTransformerRegistry.register(new JsonObjectFromAssetTransformer(jsonBuilderFactory, mapper)); dspApiTransformerRegistry.register(new JsonObjectFromQuerySpecTransformer(jsonBuilderFactory)); dspApiTransformerRegistry.register(new JsonObjectFromCriterionTransformer(jsonBuilderFactory, mapper)); @@ -181,6 +176,7 @@ private void registerV08Transformers(ObjectMapper mapper) { // EDC model to JSON-LD transformers var dspApiTransformerRegistry = transformerRegistry.forContext(DSP_TRANSFORMER_CONTEXT_V_08); + dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper)); dspApiTransformerRegistry.register(new JsonObjectFromDataAddressDspaceTransformer(jsonBuilderFactory, mapper)); } @@ -191,6 +187,7 @@ private void registerV2024Transformers(ObjectMapper mapper) { // EDC model to JSON-LD transformers var dspApiTransformerRegistry = transformerRegistry.forContext(DSP_TRANSFORMER_CONTEXT_V_2024_1); + dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper, true)); dspApiTransformerRegistry.register(new JsonObjectFromDataAddressDspace2024Transformer(jsonBuilderFactory, mapper)); } diff --git a/extensions/common/api/lib/management-api-lib/src/main/java/org/eclipse/edc/api/management/ManagementApi.java b/extensions/common/api/lib/management-api-lib/src/main/java/org/eclipse/edc/api/management/ManagementApi.java new file mode 100644 index 00000000000..3b660ab165a --- /dev/null +++ b/extensions/common/api/lib/management-api-lib/src/main/java/org/eclipse/edc/api/management/ManagementApi.java @@ -0,0 +1,22 @@ +/* + * 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.api.management; + +public interface ManagementApi { + + String MANAGEMENT_API_CONTEXT = "management-api"; + + String MANAGEMENT_API_V_4_ALPHA = "v4alpha"; +} diff --git a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java index 9ef3e076f5e..542304b8fc0 100644 --- a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java +++ b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java @@ -62,6 +62,8 @@ import static java.lang.String.format; import static java.util.Optional.ofNullable; +import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_CONTEXT; +import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_V_4_ALPHA; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; @@ -92,12 +94,9 @@ public class ManagementApiConfigurationExtension implements ServiceExtension { .useDefaultContext(true) .name(WEB_SERVICE_NAME) .build(); - + private static final boolean DEFAULT_MANAGEMENT_API_ENABLE_CONTEXT = false; @Setting(description = "Configures endpoint for reaching the Management API, in the format \"\"", key = "edc.management.endpoint", required = false) private String managementApiEndpoint; - - private static final boolean DEFAULT_MANAGEMENT_API_ENABLE_CONTEXT = false; - @Setting(description = "If set enable the usage of management api JSON-LD context.", defaultValue = "" + DEFAULT_MANAGEMENT_API_ENABLE_CONTEXT, key = "edc.management.context.enabled") private boolean managementApiContextEnabled; @@ -150,7 +149,7 @@ public void initialize(ServiceExtensionContext context) { webService.registerResource(ApiContext.MANAGEMENT, new ObjectMapperProvider(jsonLdMapper)); webService.registerResource(ApiContext.MANAGEMENT, new JerseyJsonLdInterceptor(jsonLd, jsonLdMapper, MANAGEMENT_SCOPE)); - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); + var managementApiTransformerRegistry = transformerRegistry.forContext(MANAGEMENT_API_CONTEXT); var factory = Json.createBuilderFactory(Map.of()); managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory)); @@ -167,6 +166,10 @@ public void initialize(ServiceExtensionContext context) { managementApiTransformerRegistry.register(new JsonObjectToAssetTransformer()); managementApiTransformerRegistry.register(new JsonValueToGenericTypeTransformer(jsonLdMapper)); + var managementApiTransformerRegistryV4Alpha = managementApiTransformerRegistry.forContext(MANAGEMENT_API_V_4_ALPHA); + + managementApiTransformerRegistryV4Alpha.register(new JsonObjectFromPolicyTransformer(factory, participantIdMapper, true)); + registerVersionInfo(getClass().getClassLoader()); } diff --git a/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json b/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json index 22dc74a6a0e..9a07bec56af 100644 --- a/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json +++ b/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json @@ -10,5 +10,11 @@ "urlPath": "/v3.1alpha", "lastUpdated": "2024-09-04T10:17:00Z", "maturity": "alpha" + }, + { + "version": "4.0.0-alpha", + "urlPath": "/v4alpha", + "lastUpdated": "2024-11-21T14:24:00Z", + "maturity": "alpha" } ] diff --git a/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java b/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java index 998ea0f1aed..9bf6efd0e8a 100644 --- a/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java +++ b/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java @@ -67,7 +67,9 @@ class ManagementApiConfigurationExtensionTest { @BeforeEach void setUp(ServiceExtensionContext context, ObjectFactory factory) { TypeTransformerRegistry typeTransformerRegistry = mock(); - when(typeTransformerRegistry.forContext(any())).thenReturn(mock()); + TypeTransformerRegistry contextTypeTransformerRegistry = mock(); + when(typeTransformerRegistry.forContext(any())).thenReturn(contextTypeTransformerRegistry); + when(contextTypeTransformerRegistry.forContext(any())).thenReturn(mock()); context.registerService(WebService.class, webService); context.registerService(WebServiceConfigurer.class, configurer); context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); @@ -97,7 +99,7 @@ void initialize_shouldConfigureAndRegisterResource() { @Test void initialize_withContextEnabled(ObjectFactory factory, ServiceExtensionContext context) { when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("edc.management.context.enabled", "true"))); - + var configuration = WebServiceConfiguration.Builder.newInstance().path("/path").port(1234).build(); when(configurer.configure(any(), any(), any())).thenReturn(configuration); diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java index 78fb4fa1ad3..ec8ebc59fd4 100644 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java +++ b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java @@ -17,6 +17,7 @@ import org.eclipse.edc.connector.controlplane.api.management.contractagreement.v2.ContractAgreementApiV2Controller; import org.eclipse.edc.connector.controlplane.api.management.contractagreement.v3.ContractAgreementApiV3Controller; +import org.eclipse.edc.connector.controlplane.api.management.contractagreement.v4alpha.ContractAgreementApiV4AlphaController; 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; @@ -27,6 +28,9 @@ import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; +import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_CONTEXT; +import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_V_4_ALPHA; + @Extension(value = ContractAgreementApiExtension.NAME) public class ContractAgreementApiExtension implements ServiceExtension { @@ -52,9 +56,12 @@ public String name() { public void initialize(ServiceExtensionContext context) { var monitor = context.getMonitor(); - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); + var managementApiTransformerRegistry = transformerRegistry.forContext(MANAGEMENT_API_CONTEXT); + var managementApiTransformerRegistryV31Alpha = managementApiTransformerRegistry.forContext(MANAGEMENT_API_V_4_ALPHA); + webService.registerResource(ApiContext.MANAGEMENT, new ContractAgreementApiV2Controller(service, managementApiTransformerRegistry, monitor, validatorRegistry)); webService.registerResource(ApiContext.MANAGEMENT, new ContractAgreementApiV3Controller(service, managementApiTransformerRegistry, monitor, validatorRegistry)); + webService.registerResource(ApiContext.MANAGEMENT, new ContractAgreementApiV4AlphaController(service, managementApiTransformerRegistryV31Alpha, monitor, validatorRegistry)); } } diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4Alpha.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4Alpha.java new file mode 100644 index 00000000000..39c34475867 --- /dev/null +++ b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4Alpha.java @@ -0,0 +1,71 @@ +/* + * 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.controlplane.api.management.contractagreement.v4alpha; + +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; + +@OpenAPIDefinition(info = @Info(version = "v4alpha")) +@Tag(name = "Contract Agreement v4alpha") +public interface ContractAgreementApiV4Alpha { + + @Operation(description = "Gets all 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 queryAgreementsV4Alpha(JsonObject querySpecJson); + + @Operation(description = "Gets an contract agreement with the given ID", + responses = { + @ApiResponse(responseCode = "200", description = "The contract agreement", + content = @Content(schema = @Schema(implementation = ManagementApiSchema.ContractAgreementSchema.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 contract agreement with the given ID does not exist", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + } + ) + JsonObject getAgreementByIdV4Alpha(String id); + + + @Operation(description = "Gets a contract negotiation with the given contract agreement ID", + responses = { + @ApiResponse(responseCode = "200", description = "The contract negotiation", + content = @Content(schema = @Schema(implementation = ManagementApiSchema.ContractNegotiationSchema.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 contract agreement with the given ID does not exist", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + } + ) + JsonObject getNegotiationByAgreementIdV4Alpha(String id); + +} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4AlphaController.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4AlphaController.java new file mode 100644 index 00000000000..3576ceb18a2 --- /dev/null +++ b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v4alpha/ContractAgreementApiV4AlphaController.java @@ -0,0 +1,61 @@ +/* + * 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.controlplane.api.management.contractagreement.v4alpha; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +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.connector.controlplane.api.management.contractagreement.BaseContractAgreementApiController; +import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v4alpha/contractagreements") +public class ContractAgreementApiV4AlphaController extends BaseContractAgreementApiController implements ContractAgreementApiV4Alpha { + public ContractAgreementApiV4AlphaController(ContractAgreementService service, TypeTransformerRegistry transformerRegistry, Monitor monitor, JsonObjectValidatorRegistry validatorRegistry) { + super(service, transformerRegistry, monitor, validatorRegistry); + } + + @POST + @Path("/request") + @Override + public JsonArray queryAgreementsV4Alpha(JsonObject querySpecJson) { + return queryAgreements(querySpecJson); + } + + @GET + @Path("{id}") + @Override + public JsonObject getAgreementByIdV4Alpha(@PathParam("id") String id) { + return getAgreementById(id); + } + + @GET + @Path("{id}/negotiation") + @Override + public JsonObject getNegotiationByAgreementIdV4Alpha(@PathParam("id") String id) { + return getNegotiationByAgreementId(id); + } +} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java index b39fe69f4dc..d6a2e334c75 100644 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java +++ b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java @@ -18,6 +18,7 @@ import org.eclipse.edc.connector.controlplane.api.management.contractagreement.v3.ContractAgreementApiV3Controller; 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.web.spi.WebService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,14 +28,20 @@ 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 ContractAgreementApiExtensionTest { private final WebService webService = mock(WebService.class); + private final TypeTransformerRegistry transformerRegistry = mock(); + @BeforeEach void setUp(ServiceExtensionContext context) { context.registerService(WebService.class, webService); + context.registerService(TypeTransformerRegistry.class, transformerRegistry); + + when(transformerRegistry.forContext(any())).thenReturn(mock()); } @Test diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v31alpha/ContractAgreementApiV4AlphaControllerTest.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v31alpha/ContractAgreementApiV4AlphaControllerTest.java new file mode 100644 index 00000000000..2f34c85fc8a --- /dev/null +++ b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractagreement/v31alpha/ContractAgreementApiV4AlphaControllerTest.java @@ -0,0 +1,34 @@ +/* + * 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.controlplane.api.management.contractagreement.v31alpha; + +import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.connector.controlplane.api.management.contractagreement.BaseContractAgreementApiControllerTest; +import org.eclipse.edc.connector.controlplane.api.management.contractagreement.v4alpha.ContractAgreementApiV4AlphaController; + +import static io.restassured.RestAssured.given; + +class ContractAgreementApiV4AlphaControllerTest extends BaseContractAgreementApiControllerTest { + @Override + protected Object controller() { + return new ContractAgreementApiV4AlphaController(service, transformerRegistry, monitor, validatorRegistry); + } + + protected RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port + "/v4alpha/contractagreements") + .when(); + } +} \ No newline at end of file diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java index 8188cf24012..d5c0504ba9f 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java @@ -33,7 +33,9 @@ import static io.restassured.http.ContentType.JSON; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; public class ContractAgreementApiEndToEndTest { @@ -62,18 +64,37 @@ void getAll(ManagementEndToEndTestContext context, ContractNegotiationStore stor @Test void getById(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); + var agreement = createContractAgreement("cn1"); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - var json = context.baseRequest() + context.baseRequest() .contentType(JSON) .get("/v3/contractagreements/cn1") .then() .statusCode(200) .contentType(JSON) - .extract().jsonPath(); + .body(ID, is(agreement.getId())) + .body("assetId", notNullValue()) + .body("policy.'odrl:assignee'", is(agreement.getPolicy().getAssignee())) + .body("policy.'odrl:assigner'", is(agreement.getPolicy().getAssigner())); - assertThat(json.getString("@id")).isEqualTo("cn1"); - assertThat(json.getString("assetId")).isNotNull(); + } + + @Test + void getByIdV4Alpha(ManagementEndToEndTestContext context, ContractNegotiationStore store) { + var agreement = createContractAgreement("cn1"); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); + + context.baseRequest() + .contentType(JSON) + .get("/v4alpha/contractagreements/cn1") + .then() + .statusCode(200) + .contentType(JSON) + .body(ID, is(agreement.getId())) + .body("assetId", notNullValue()) + .body("policy.'odrl:assignee'.'@id'", is(agreement.getPolicy().getAssignee())) + .body("policy.'odrl:assigner'.'@id'", is(agreement.getPolicy().getAssigner())); } @Test @@ -120,7 +141,7 @@ private ContractAgreement createContractAgreement(String negotiationId) { .assetId(UUID.randomUUID().toString()) .consumerId(UUID.randomUUID() + "-consumer") .providerId(UUID.randomUUID() + "-provider") - .policy(Policy.Builder.newInstance().build()) + .policy(Policy.Builder.newInstance().assignee("assignee").assigner("assigner").build()) .build(); } } @@ -128,10 +149,12 @@ private ContractAgreement createContractAgreement(String negotiationId) { @Nested @EndToEndTest @ExtendWith(ManagementEndToEndExtension.InMemory.class) - class InMemory extends Tests { } + class InMemory extends Tests { + } @Nested @PostgresqlIntegrationTest @ExtendWith(ManagementEndToEndExtension.Postgres.class) - class Postgres extends Tests { } + class Postgres extends Tests { + } } diff --git a/system-tests/version-api/version-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/versionapi/VersionApiEndToEndTest.java b/system-tests/version-api/version-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/versionapi/VersionApiEndToEndTest.java index f96a5a47c4d..00a9e18d1b5 100644 --- a/system-tests/version-api/version-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/versionapi/VersionApiEndToEndTest.java +++ b/system-tests/version-api/version-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/versionapi/VersionApiEndToEndTest.java @@ -51,9 +51,10 @@ void getVersion() { }); assertThat(result).containsKeys("management", "version", "control", "observability", "sts"); - assertThat(result.get("management")).hasSize(2) + assertThat(result.get("management")).hasSize(3) .anyMatch(vr -> vr.version().startsWith("3.") && vr.maturity().equals("stable")) - .anyMatch(vr -> vr.version().equals("3.1.0-alpha") && vr.maturity().equals("alpha")); + .anyMatch(vr -> vr.version().equals("3.1.0-alpha") && vr.maturity().equals("alpha")) + .anyMatch(vr -> vr.version().equals("4.0.0-alpha") && vr.maturity().equals("alpha")); assertThat(result.get("version")).hasSize(1).anyMatch(vr -> vr.version().equals("1.0.0")); assertThat(result.get("observability")).hasSize(1).anyMatch(vr -> vr.version().equals("1.0.0")); assertThat(result.get("sts")).hasSize(1).anyMatch(vr -> vr.version().equals("1.0.0"));