Skip to content

Commit 83b9245

Browse files
authored
feat: port v5alpha catalog management api for virtual edc (#5603)
* Feat: port v5alpha catalog management api for virtual edc * chore: fix open api verification * chore: fix open api verification
1 parent 5e694f8 commit 83b9245

17 files changed

Lines changed: 1355 additions & 26 deletions

File tree

data-protocols/data-plane-signaling/src/main/resources/signaling-api-version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
"version": "1.0.0-alpha",
44
"urlPath": "/v1",
5-
"lastUpdated": "2026-03-27T12:00:01Z",
5+
"lastUpdated": "2026-03-27T13:00:01Z",
66
"maturity": "alpha"
77
}
88
]

extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
{
1515
"version": "5.0.0-alpha",
1616
"urlPath": "/v5alpha",
17-
"lastUpdated": "2026-03-24T09:00:00Z",
17+
"lastUpdated": "2026-03-27T09:00:00Z",
1818
"maturity": "alpha"
1919
}
2020
]

extensions/control-plane/api/management-api-v5/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
api(project(":extensions:control-plane:api:management-api-v5:asset-api-v5"))
2323
api(project(":extensions:control-plane:api:management-api-v5:contract-definition-api-v5"))
2424
api(project(":extensions:control-plane:api:management-api-v5:policy-definition-api-v5"))
25+
api(project(":extensions:control-plane:api:management-api-v5:catalog-api-v5"))
2526
api(project(":extensions:control-plane:api:management-api-v5:contract-negotiation-api-v5"))
2627
api(project(":extensions:control-plane:api:management-api-v5:contract-agreement-api-v5"))
2728
api(project(":extensions:control-plane:api:management-api-v5:transfer-process-api-v5"))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
plugins {
15+
`java-library`
16+
id(libs.plugins.swagger.get().pluginId)
17+
}
18+
19+
dependencies {
20+
api(project(":spi:common:auth-spi"))
21+
api(project(":spi:control-plane:control-plane-spi"))
22+
api(project(":spi:common:participant-context-single-spi"))
23+
24+
implementation(project(":core:common:lib:api-lib"))
25+
implementation(project(":core:common:lib:validator-lib"))
26+
implementation(project(":extensions:common:api:lib:management-api-lib"))
27+
implementation(project(":extensions:common:http:lib:jersey-providers-lib"))
28+
implementation(project(":core:control-plane:control-plane-transform"))
29+
30+
implementation(libs.jakarta.rsApi)
31+
implementation(libs.jakarta.annotation)
32+
33+
testImplementation(project(":core:common:junit"))
34+
testImplementation(project(":core:common:lib:transform-lib"))
35+
testImplementation(project(":core:control-plane:control-plane-core"))
36+
testImplementation(project(":core:data-plane-selector:data-plane-selector-core"))
37+
testImplementation(project(":extensions:common:http"))
38+
testImplementation(project(":extensions:common:iam:iam-mock"))
39+
testImplementation(testFixtures(project(":extensions:common:http:jersey-core")))
40+
testImplementation(libs.restAssured)
41+
}
42+
43+
edcBuild {
44+
swagger {
45+
apiGroup.set("management-api")
46+
}
47+
}
48+
49+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.connector.controlplane.api.management.catalog;
16+
17+
import org.eclipse.edc.api.auth.spi.AuthorizationService;
18+
import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema;
19+
import org.eclipse.edc.connector.controlplane.api.management.catalog.v5.CatalogApiV5Controller;
20+
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogService;
21+
import org.eclipse.edc.connector.controlplane.transform.edc.catalog.to.JsonObjectToCatalogRequestTransformer;
22+
import org.eclipse.edc.connector.controlplane.transform.edc.catalog.to.JsonObjectToDatasetRequestTransformer;
23+
import org.eclipse.edc.jsonld.spi.JsonLd;
24+
import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService;
25+
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
26+
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
27+
import org.eclipse.edc.spi.system.ServiceExtension;
28+
import org.eclipse.edc.spi.system.ServiceExtensionContext;
29+
import org.eclipse.edc.spi.types.TypeManager;
30+
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
31+
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
32+
import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor;
33+
import org.eclipse.edc.web.spi.WebService;
34+
import org.eclipse.edc.web.spi.configuration.ApiContext;
35+
36+
import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4;
37+
import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD;
38+
39+
@Extension(value = CatalogApiV5Extension.NAME)
40+
public class CatalogApiV5Extension implements ServiceExtension {
41+
42+
public static final String NAME = "Management API: Catalog";
43+
44+
@Inject
45+
private WebService webService;
46+
47+
@Inject
48+
private TypeTransformerRegistry transformerRegistry;
49+
50+
@Inject
51+
private CatalogService service;
52+
53+
@Inject
54+
private JsonObjectValidatorRegistry validatorRegistry;
55+
56+
@Inject
57+
private JsonLd jsonLd;
58+
59+
@Inject
60+
private TypeManager typeManager;
61+
@Inject
62+
private AuthorizationService authorizationService;
63+
@Inject
64+
private ParticipantContextService participantContextService;
65+
66+
@Override
67+
public String name() {
68+
return NAME;
69+
}
70+
71+
@Override
72+
public void initialize(ServiceExtensionContext context) {
73+
transformerRegistry.register(new JsonObjectToCatalogRequestTransformer());
74+
transformerRegistry.register(new JsonObjectToDatasetRequestTransformer());
75+
76+
var managementApiTransformerRegistry = transformerRegistry.forContext("management-api");
77+
78+
// authorization service does need an additional lookup function - catalogs are not a persisted entity
79+
80+
webService.registerResource(ApiContext.MANAGEMENT, new CatalogApiV5Controller(service, managementApiTransformerRegistry, authorizationService, participantContextService));
81+
webService.registerDynamicResource(ApiContext.MANAGEMENT, CatalogApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version()));
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.connector.controlplane.api.management.catalog.v5;
16+
17+
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
18+
import io.swagger.v3.oas.annotations.Operation;
19+
import io.swagger.v3.oas.annotations.info.Info;
20+
import io.swagger.v3.oas.annotations.media.Content;
21+
import io.swagger.v3.oas.annotations.media.Schema;
22+
import io.swagger.v3.oas.annotations.parameters.RequestBody;
23+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
24+
import io.swagger.v3.oas.annotations.tags.Tag;
25+
import jakarta.json.JsonObject;
26+
import jakarta.ws.rs.container.AsyncResponse;
27+
import jakarta.ws.rs.container.Suspended;
28+
import jakarta.ws.rs.core.SecurityContext;
29+
import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema;
30+
31+
@OpenAPIDefinition(info = @Info(version = "v5alpha"))
32+
@Tag(name = "Catalog v5alpha")
33+
public interface CatalogApiV5 {
34+
35+
@Operation(
36+
requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CATALOG_REQUEST))),
37+
responses = {@ApiResponse(
38+
content = @Content(
39+
mediaType = "application/json",
40+
schema = @Schema(ref = "https://w3id.org/dspace/2025/1/catalog/catalog-schema.json")
41+
),
42+
description = "Gets contract offers (=catalog) of a single connector")}
43+
)
44+
void requestCatalogV5(String participantContextId, JsonObject request, @Suspended AsyncResponse response, SecurityContext context);
45+
46+
@Operation(
47+
requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CATALOG_REQUEST))),
48+
responses = {@ApiResponse(
49+
content = @Content(
50+
mediaType = "application/json",
51+
schema = @Schema(ref = "https://w3id.org/dspace/2025/1/catalog/dataset-schema.json")
52+
),
53+
description = "Gets single dataset from a connector")}
54+
)
55+
void getDatasetV5(String participantContextId, JsonObject request, @Suspended AsyncResponse response, SecurityContext context);
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.connector.controlplane.api.management.catalog.v5;
16+
17+
import jakarta.annotation.security.RolesAllowed;
18+
import jakarta.json.JsonObject;
19+
import jakarta.ws.rs.Consumes;
20+
import jakarta.ws.rs.POST;
21+
import jakarta.ws.rs.Path;
22+
import jakarta.ws.rs.PathParam;
23+
import jakarta.ws.rs.Produces;
24+
import jakarta.ws.rs.container.AsyncResponse;
25+
import jakarta.ws.rs.container.Suspended;
26+
import jakarta.ws.rs.core.Context;
27+
import jakarta.ws.rs.core.SecurityContext;
28+
import org.eclipse.edc.api.auth.spi.AuthorizationService;
29+
import org.eclipse.edc.api.auth.spi.ParticipantPrincipal;
30+
import org.eclipse.edc.api.auth.spi.RequiredScope;
31+
import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequest;
32+
import org.eclipse.edc.connector.controlplane.catalog.spi.DatasetRequest;
33+
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogService;
34+
import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService;
35+
import org.eclipse.edc.participantcontext.spi.types.ParticipantContext;
36+
import org.eclipse.edc.spi.EdcException;
37+
import org.eclipse.edc.spi.response.StatusResult;
38+
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
39+
import org.eclipse.edc.web.spi.exception.BadGatewayException;
40+
import org.eclipse.edc.web.spi.exception.InvalidRequestException;
41+
import org.eclipse.edc.web.spi.validation.SchemaType;
42+
43+
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
44+
import static org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequest.CATALOG_REQUEST_TYPE_TERM;
45+
import static org.eclipse.edc.connector.controlplane.catalog.spi.DatasetRequest.DATASET_REQUEST_TYPE_TERM;
46+
import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper;
47+
48+
@Consumes(APPLICATION_JSON)
49+
@Produces(APPLICATION_JSON)
50+
@Path("/v5alpha/participants/{participantContextId}/catalog")
51+
public class CatalogApiV5Controller implements CatalogApiV5 {
52+
private final AuthorizationService authorizationService;
53+
private final CatalogService service;
54+
private final TypeTransformerRegistry transformerRegistry;
55+
private final ParticipantContextService participantContextService;
56+
57+
public CatalogApiV5Controller(CatalogService service, TypeTransformerRegistry transformerRegistry,
58+
AuthorizationService authorizationService, ParticipantContextService participantContextService) {
59+
this.service = service;
60+
this.transformerRegistry = transformerRegistry;
61+
this.authorizationService = authorizationService;
62+
this.participantContextService = participantContextService;
63+
}
64+
65+
66+
@POST
67+
@Path("/request")
68+
@RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT})
69+
@RequiredScope("management-api:read")
70+
@Override
71+
public void requestCatalogV5(@PathParam("participantContextId") String participantContextId,
72+
@SchemaType(CATALOG_REQUEST_TYPE_TERM) JsonObject requestBody,
73+
@Suspended AsyncResponse response,
74+
@Context SecurityContext securityContext) {
75+
76+
var participantContext = preAuthorize(participantContextId, securityContext);
77+
78+
var request = transformerRegistry.transform(requestBody, CatalogRequest.class)
79+
.orElseThrow(InvalidRequestException::new);
80+
81+
var scopes = request.getAdditionalScopes().toArray(new String[0]);
82+
service.requestCatalog(participantContext, request.getCounterPartyId(), request.getCounterPartyAddress(), request.getProtocol(), request.getQuerySpec(), scopes)
83+
.whenComplete((result, throwable) -> {
84+
try {
85+
response.resume(toResponse(result, throwable));
86+
} catch (Throwable mapped) {
87+
response.resume(mapped);
88+
}
89+
});
90+
}
91+
92+
@POST
93+
@Path("/dataset/request")
94+
@RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT})
95+
@RequiredScope("management-api:read")
96+
@Override
97+
public void getDatasetV5(@PathParam("participantContextId") String participantContextId,
98+
@SchemaType(DATASET_REQUEST_TYPE_TERM) JsonObject requestBody,
99+
@Suspended AsyncResponse response,
100+
@Context SecurityContext securityContext) {
101+
102+
var participantContext = preAuthorize(participantContextId, securityContext);
103+
104+
var request = transformerRegistry.transform(requestBody, DatasetRequest.class)
105+
.orElseThrow(InvalidRequestException::new);
106+
107+
service.requestDataset(participantContext, request.getId(), request.getCounterPartyId(), request.getCounterPartyAddress(), request.getProtocol())
108+
.whenComplete((result, throwable) -> {
109+
try {
110+
response.resume(toResponse(result, throwable));
111+
} catch (Throwable mapped) {
112+
response.resume(mapped);
113+
}
114+
});
115+
}
116+
117+
/**
118+
* utility method that performs authorization on the participant context with the given ID and return the {@link ParticipantContext}
119+
* throws an exception if the authorization fails. the exception does not need to be caught.
120+
*/
121+
122+
private ParticipantContext preAuthorize(String participantContextId, SecurityContext securityContext) {
123+
authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class)
124+
.orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId));
125+
126+
return participantContextService.getParticipantContext(participantContextId)
127+
.orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId));
128+
}
129+
130+
private byte[] toResponse(StatusResult<byte[]> result, Throwable throwable) throws Throwable {
131+
if (throwable == null) {
132+
if (result.succeeded()) {
133+
return result.getContent();
134+
} else {
135+
throw new BadGatewayException(result.getFailureDetail());
136+
}
137+
} else {
138+
if (throwable instanceof EdcException || throwable.getCause() instanceof EdcException) {
139+
throw new BadGatewayException(throwable.getMessage());
140+
} else {
141+
throw throwable;
142+
}
143+
}
144+
}
145+
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#
2+
# Copyright (c) 2025 Metaform Systems, Inc.
3+
#
4+
# This program and the accompanying materials are made available under the
5+
# terms of the Apache License, Version 2.0 which is available at
6+
# https://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# SPDX-License-Identifier: Apache-2.0
9+
#
10+
# Contributors:
11+
# Metaform Systems, Inc. - initial API and implementation
12+
#
13+
#
14+
15+
org.eclipse.edc.connector.controlplane.api.management.catalog.CatalogApiV5Extension

0 commit comments

Comments
 (0)