Skip to content

Commit 65f6c2e

Browse files
authored
feat: discovery endpoint (#5768)
1 parent 847b3d6 commit 65f6c2e

34 files changed

Lines changed: 1885 additions & 1 deletion

File tree

core/common/lib/json-ld-lib/src/main/resources/document/management-context-v2.jsonld

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,34 @@
562562
}
563563
}
564564
},
565+
"DiscoveryRequest": {
566+
"@id": "edc:DiscoveryRequest",
567+
"@context": {
568+
"counterPartyId": {
569+
"@id": "edc:counterPartyId"
570+
},
571+
"counterPartyAddress": {
572+
"@id": "edc:counterPartyAddress"
573+
}
574+
}
575+
},
576+
"DiscoveryResponse": {
577+
"@id": "edc:DiscoveryResponse",
578+
"@context": {
579+
"profile": {
580+
"@id": "edc:profile"
581+
},
582+
"version": {
583+
"@id": "edc:version"
584+
},
585+
"counterPartyPath": {
586+
"@id": "edc:counterPartyPath"
587+
},
588+
"binding": {
589+
"@id": "edc:binding"
590+
}
591+
}
592+
},
565593
"inForceDate": "edc:inForceDate",
566594
"ruleFunctions": {
567595
"@id": "edc:ruleFunctions",

core/control-plane/control-plane-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ plugins {
2020
dependencies {
2121
api(project(":spi:control-plane:asset-spi"))
2222
api(project(":spi:control-plane:control-plane-spi"))
23+
api(project(":spi:common:http-spi"))
2324

2425
implementation(project(":core:common:lib:store-lib"))
2526
implementation(project(":core:common:boot"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.discovery;
16+
17+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryRequest;
18+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryUrlResolver;
19+
import org.eclipse.edc.spi.result.ServiceResult;
20+
21+
import java.util.Objects;
22+
23+
public class DefaultUrlResolver implements DiscoveryUrlResolver {
24+
25+
static final String WELL_KNOWN_PATH = "/.well-known/dspace-version";
26+
27+
@Override
28+
public ServiceResult<String> resolve(DiscoveryRequest request) {
29+
return ServiceResult.success(toWellKnownUrl(Objects.requireNonNull(request.counterPartyAddress())));
30+
}
31+
32+
@Override
33+
public boolean canResolve(DiscoveryRequest request) {
34+
return request.counterPartyAddress() != null && !request.counterPartyAddress().isBlank() && request.counterPartyId() == null;
35+
}
36+
37+
private String toWellKnownUrl(String baseUrl) {
38+
var trimmed = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
39+
return trimmed.endsWith(WELL_KNOWN_PATH) ? trimmed : trimmed + WELL_KNOWN_PATH;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.discovery;
16+
17+
import org.eclipse.edc.http.spi.EdcHttpClient;
18+
import org.eclipse.edc.protocol.spi.ParticipantProfileService;
19+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryService;
20+
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
21+
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
22+
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
23+
import org.eclipse.edc.spi.system.ServiceExtension;
24+
import org.eclipse.edc.spi.types.TypeManager;
25+
26+
@Extension(value = DiscoveryDefaultServicesExtension.NAME)
27+
public class DiscoveryDefaultServicesExtension implements ServiceExtension {
28+
29+
public static final String NAME = "Discovery Default Services";
30+
31+
@Inject
32+
private EdcHttpClient httpClient;
33+
@Inject
34+
private ParticipantProfileService participantProfileService;
35+
@Inject
36+
private TypeManager typeManager;
37+
38+
@Override
39+
public String name() {
40+
return NAME;
41+
}
42+
43+
@Provider(isDefault = true)
44+
public DiscoveryService discoveryService() {
45+
return new DiscoveryServiceImpl(httpClient, participantProfileService, typeManager.getMapper());
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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.discovery;
16+
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import okhttp3.Request;
19+
import okhttp3.Response;
20+
import org.eclipse.edc.http.spi.EdcHttpClient;
21+
import org.eclipse.edc.protocol.spi.DataspaceProfileContext;
22+
import org.eclipse.edc.protocol.spi.ParticipantProfileService;
23+
import org.eclipse.edc.protocol.spi.ProtocolVersion;
24+
import org.eclipse.edc.protocol.spi.ProtocolVersions;
25+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryRequest;
26+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryResponse;
27+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryService;
28+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryUrlResolver;
29+
import org.eclipse.edc.spi.result.Result;
30+
import org.eclipse.edc.spi.result.ServiceResult;
31+
32+
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.function.Function;
36+
import java.util.stream.Stream;
37+
38+
public class DiscoveryServiceImpl implements DiscoveryService {
39+
40+
private final EdcHttpClient httpClient;
41+
private final ParticipantProfileService participantProfileService;
42+
private final ObjectMapper objectMapper;
43+
private final List<DiscoveryUrlResolver> resolvers = new ArrayList<>();
44+
45+
public DiscoveryServiceImpl(
46+
EdcHttpClient httpClient,
47+
ParticipantProfileService participantProfileService,
48+
ObjectMapper objectMapper) {
49+
this.httpClient = httpClient;
50+
this.participantProfileService = participantProfileService;
51+
this.objectMapper = objectMapper;
52+
}
53+
54+
55+
@Override
56+
public ServiceResult<List<DiscoveryResponse>> discover(String participantContextId, DiscoveryRequest request) {
57+
58+
var resolver = resolvers.stream()
59+
.filter(r -> r.canResolve(request))
60+
.findFirst()
61+
.orElse(null);
62+
63+
if (resolver != null) {
64+
return resolveMatches(participantContextId, request, resolver);
65+
66+
} else {
67+
return ServiceResult.badRequest("No resolver found for request: %s".formatted(request));
68+
}
69+
}
70+
71+
private ServiceResult<List<DiscoveryResponse>> resolveMatches(String participantContextId, DiscoveryRequest request, DiscoveryUrlResolver resolver) {
72+
73+
var versionsResult = resolver.resolve(request).compose(this::fetchProtocolVersions);
74+
if (versionsResult.failed()) {
75+
return ServiceResult.badRequest(versionsResult.getFailureDetail());
76+
}
77+
78+
var localProfiles = participantProfileService.resolveAll(participantContextId);
79+
var matches = localProfiles.stream()
80+
.flatMap(profileMatcher(versionsResult.getContent()))
81+
.toList();
82+
83+
return ServiceResult.success(matches);
84+
}
85+
86+
private Function<DataspaceProfileContext, Stream<? extends DiscoveryResponse>> profileMatcher(List<ProtocolVersion> versions) {
87+
return profile -> versions.stream()
88+
.filter(v -> v.version().equals(profile.protocolVersion().version()) &&
89+
v.binding().equals(profile.protocolVersion().binding()))
90+
.map(v -> new DiscoveryResponse(
91+
profile.name(),
92+
v.version(),
93+
v.path(),
94+
v.binding()));
95+
}
96+
97+
98+
private ServiceResult<List<ProtocolVersion>> fetchProtocolVersions(String url) {
99+
var request = new Request.Builder().url(url).get().build();
100+
var result = httpClient.execute(request, response -> handleResponse(response, url));
101+
102+
if (result.failed()) {
103+
return ServiceResult.badRequest(result.getFailureDetail());
104+
}
105+
return ServiceResult.success(result.getContent());
106+
}
107+
108+
private Result<List<ProtocolVersion>> handleResponse(Response response, String url) {
109+
if (!response.isSuccessful()) {
110+
return Result.failure("Unexpected status %d fetching '%s'".formatted(response.code(), url));
111+
}
112+
try (var body = response.body()) {
113+
var versions = objectMapper.readValue(body.byteStream(), ProtocolVersions.class);
114+
return Result.success(versions.protocolVersions());
115+
} catch (IOException e) {
116+
return Result.failure("Failed to parse '%s': %s".formatted(url, e.getMessage()));
117+
}
118+
}
119+
120+
@Override
121+
public void registerResolver(DiscoveryUrlResolver resolver) {
122+
resolvers.add(resolver);
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.discovery;
16+
17+
import org.eclipse.edc.protocol.spi.discovery.DiscoveryService;
18+
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
19+
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
20+
import org.eclipse.edc.spi.system.ServiceExtension;
21+
import org.eclipse.edc.spi.system.ServiceExtensionContext;
22+
23+
@Extension(value = DiscoveryServicesExtension.NAME)
24+
public class DiscoveryServicesExtension implements ServiceExtension {
25+
26+
public static final String NAME = "Discovery Services";
27+
28+
@Inject
29+
private DiscoveryService discoveryService;
30+
31+
@Override
32+
public String name() {
33+
return NAME;
34+
}
35+
36+
@Override
37+
public void initialize(ServiceExtensionContext context) {
38+
discoveryService.registerResolver(new DefaultUrlResolver());
39+
}
40+
}

core/control-plane/control-plane-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
#
1414

1515
org.eclipse.edc.connector.controlplane.ControlPlaneDefaultServicesExtension
16+
org.eclipse.edc.connector.controlplane.discovery.DiscoveryDefaultServicesExtension
17+
org.eclipse.edc.connector.controlplane.discovery.DiscoveryServicesExtension

0 commit comments

Comments
 (0)