Skip to content

Commit 4a0a602

Browse files
authored
feat: implements DCP scope mgmt api (#5845)
1 parent dc3cef5 commit 4a0a602

30 files changed

Lines changed: 1454 additions & 11 deletions

File tree

.github/workflows/publish-context.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ on:
2626
push:
2727
branches: [ main ]
2828
paths:
29-
- 'core/common/lib/json-ld-lib/src/main/resources/document/**'
29+
- 'core/common/lib/jsonld-lib/src/main/resources/document/**'
3030
- 'extensions/common/api/management-api-schema-validator/src/main/resources/schema/management/**'
3131

3232
jobs:
@@ -40,9 +40,9 @@ jobs:
4040
- name: copy contexts into public folder
4141
run: |
4242
mkdir -p public/context
43-
cp core/common/lib/json-ld-lib/src/main/resources/document/management-context-v1.jsonld public/context/
44-
cp core/common/lib/json-ld-lib/src/main/resources/document/management-context-v2.jsonld public/context/
45-
cp core/common/lib/json-ld-lib/src/main/resources/document/dspace-edc-context-v1.jsonld public/context/
43+
cp core/common/lib/jsonld-lib/src/main/resources/document/management-context-v1.jsonld public/context/
44+
cp core/common/lib/jsonld-lib/src/main/resources/document/management-context-v2.jsonld public/context/
45+
cp core/common/lib/jsonld-lib/src/main/resources/document/dspace-edc-context-v1.jsonld public/context/
4646
mkdir -p public/schema
4747
cp -r extensions/common/api/management-api-schema-validator/src/main/resources/schema/management public/schema/
4848
- name: deploy to gh-pages

core/common/lib/core-lib/src/main/java/org/eclipse/edc/api/management/schema/ManagementApiJsonSchema.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ interface V4 {
5555
String ASSOCIATE_DATASPACE_PROFILE_CONTEXT = EDC_MGMT_V4_SCHEMA_PREFIX + "/associate-dataspace-profile-schema.json";
5656
String DISCOVERY_REQUEST = EDC_MGMT_V4_SCHEMA_PREFIX + "/discovery-request-schema.json";
5757
String DISCOVERY_RESPONSE = EDC_MGMT_V4_SCHEMA_PREFIX + "/discovery-response-schema.json";
58+
String DCP_SCOPE = EDC_MGMT_V4_SCHEMA_PREFIX + "/dcp-scope-schema.json";
5859

5960

6061
static String version() {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,15 @@
612612
}
613613
}
614614
},
615+
"DcpScope": {
616+
"@id": "edc:DcpScope",
617+
"@context": {
618+
"type": "edc:type",
619+
"value": "edc:value",
620+
"profile": "edc:profile",
621+
"prefixMapping": "edc:prefixMapping"
622+
}
623+
},
615624
"inForceDate": "edc:inForceDate",
616625
"ruleFunctions": {
617626
"@id": "edc:ruleFunctions",

dist/bom/controlplane-virtual-feature-dcp-bom/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ plugins {
1919
dependencies {
2020
api(project(":dist:bom:controlplane-feature-dcp-bom"))
2121
api(project(":extensions:common:iam:decentralized-claims:decentralized-claims-cel"))
22+
api(project(":extensions:control-plane:api:management-api-v5:dcp-scope-api-v5"))
2223
}
2324

2425
edcBuild {

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-beta",
1616
"urlPath": "/v5beta",
17-
"lastUpdated": "2026-06-11T16:00:00Z",
17+
"lastUpdated": "2026-06-25T16:00:00Z",
1818
"maturity": "beta"
1919
}
2020
]

extensions/common/api/management-api-schema-validator/src/main/java/org/eclipse/edc/connector/api/management/schema/ManagementApiSchemaValidatorExtension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public class ManagementApiSchemaValidatorExtension implements ServiceExtension {
121121
put(CEL_EXPRESSION_TEST_REQUEST_TYPE_TERM, V4.CEL_EXPRESSION_TEST_REQUEST);
122122
put("AssociateDataspaceProfile", V4.ASSOCIATE_DATASPACE_PROFILE_CONTEXT);
123123
put("DiscoveryRequest", V4.DISCOVERY_REQUEST);
124+
put("DcpScope", V4.DCP_SCOPE);
124125
}
125126
};
126127

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2019-09/schema",
3+
"title": "DcpScopeSchema",
4+
"type": "object",
5+
"allOf": [
6+
{
7+
"$ref": "#/definitions/DcpScope"
8+
}
9+
],
10+
"$id": "https://w3id.org/edc/connector/management/schema/v4/dcp-scope-schema.json",
11+
"definitions": {
12+
"DcpScope": {
13+
"type": "object",
14+
"properties": {
15+
"@context": {
16+
"$ref": "https://w3id.org/edc/connector/management/schema/v4/context-schema.json"
17+
},
18+
"@type": {
19+
"type": "string",
20+
"const": "DcpScope"
21+
},
22+
"@id": {
23+
"type": "string"
24+
},
25+
"type": {
26+
"type": "string",
27+
"enum": [
28+
"DEFAULT",
29+
"POLICY"
30+
]
31+
},
32+
"value": {
33+
"type": "string"
34+
},
35+
"profile": {
36+
"type": "string"
37+
},
38+
"prefixMapping": {
39+
"type": "string"
40+
}
41+
},
42+
"required": [
43+
"@context",
44+
"@type",
45+
"value"
46+
]
47+
}
48+
}
49+
}

extensions/common/iam/decentralized-claims/decentralized-claims-core/src/main/java/org/eclipse/edc/iam/decentralizedclaims/core/scope/DcpScopeRegistryImpl.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import org.eclipse.edc.spi.query.Criterion;
2121
import org.eclipse.edc.spi.query.QuerySpec;
2222
import org.eclipse.edc.spi.result.ServiceResult;
23+
import org.eclipse.edc.spi.result.StoreResult;
2324
import org.eclipse.edc.transaction.spi.TransactionContext;
2425

2526
import java.util.List;
2627

28+
import static org.eclipse.edc.spi.query.Criterion.criterion;
29+
2730
/**
2831
* Implementation of {@link DcpScopeRegistry}.
2932
*/
@@ -42,11 +45,41 @@ public ServiceResult<Void> register(DcpScope scope) {
4245
return transactionContext.execute(() -> store.save(scope).flatMap(ServiceResult::from));
4346
}
4447

48+
@Override
49+
public ServiceResult<Void> create(DcpScope scope) {
50+
return transactionContext.execute(() -> findById(scope.getId())
51+
.compose(existing -> existing.isEmpty()
52+
? store.save(scope)
53+
: StoreResult.<Void>alreadyExists("DcpScope with id %s already exists".formatted(scope.getId())))
54+
.flatMap(ServiceResult::from));
55+
}
56+
57+
@Override
58+
public ServiceResult<Void> update(DcpScope scope) {
59+
return transactionContext.execute(() -> findById(scope.getId())
60+
.compose(existing -> existing.isEmpty()
61+
? StoreResult.<Void>notFound("DcpScope with id %s does not exist".formatted(scope.getId()))
62+
: store.save(scope))
63+
.flatMap(ServiceResult::from));
64+
}
65+
66+
@Override
67+
public ServiceResult<List<DcpScope>> query(QuerySpec spec) {
68+
return transactionContext.execute(() -> store.query(spec).flatMap(ServiceResult::from));
69+
}
70+
4571
@Override
4672
public ServiceResult<Void> remove(String scopeId) {
4773
return transactionContext.execute(() -> store.delete(scopeId).flatMap(ServiceResult::from));
4874
}
4975

76+
private StoreResult<List<DcpScope>> findById(String id) {
77+
var query = QuerySpec.Builder.newInstance()
78+
.filter(criterion("id", "=", id))
79+
.build();
80+
return store.query(query);
81+
}
82+
5083
@Override
5184
public ServiceResult<List<DcpScope>> getDefaultScopes() {
5285
var query = QuerySpec.Builder.newInstance()

extensions/common/iam/decentralized-claims/decentralized-claims-core/src/main/java/org/eclipse/edc/iam/decentralizedclaims/core/scope/defaults/InMemoryDcpScopeStore.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ public StoreResult<Void> save(DcpScope scope) {
4848

4949
@Override
5050
public StoreResult<Void> delete(String scopeId) {
51-
scopes.remove(scopeId);
52-
return StoreResult.success();
51+
return scopes.remove(scopeId) != null
52+
? StoreResult.success()
53+
: StoreResult.notFound(notFoundErrorMessage(scopeId));
5354
}
5455

56+
5557
@Override
5658
public StoreResult<List<DcpScope>> query(QuerySpec spec) {
5759
return StoreResult.success(queryResolver.query(scopes.values().stream(), spec)

extensions/common/iam/decentralized-claims/decentralized-claims-core/src/test/java/org/eclipse/edc/iam/decentralizedclaims/core/scope/DcpScopeRegistryImplTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.eclipse.edc.iam.decentralizedclaims.spi.scope.DcpScope;
1818
import org.eclipse.edc.iam.decentralizedclaims.spi.scope.store.DcpScopeStore;
19+
import org.eclipse.edc.spi.query.QuerySpec;
1920
import org.eclipse.edc.spi.result.StoreResult;
2021
import org.eclipse.edc.transaction.spi.NoopTransactionContext;
2122
import org.eclipse.edc.transaction.spi.TransactionContext;
@@ -26,6 +27,7 @@
2627
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
2728
import static org.mockito.Mockito.any;
2829
import static org.mockito.Mockito.mock;
30+
import static org.mockito.Mockito.never;
2931
import static org.mockito.Mockito.verify;
3032
import static org.mockito.Mockito.when;
3133

@@ -71,6 +73,69 @@ void register_should_return_failure_when_store_fails() {
7173
verify(store).save(scope);
7274
}
7375

76+
@Test
77+
void create_should_save_when_scope_does_not_exist() {
78+
var scope = DcpScope.Builder.newInstance().id("s1").value("v").profile("p").build();
79+
when(store.query(any())).thenReturn(StoreResult.success(List.of()));
80+
when(store.save(scope)).thenReturn(StoreResult.success());
81+
82+
var impl = new DcpScopeRegistryImpl(transactionContext, store);
83+
var res = impl.create(scope);
84+
85+
assertThat(res).isSucceeded();
86+
verify(store).save(scope);
87+
}
88+
89+
@Test
90+
void create_should_return_conflict_when_scope_already_exists() {
91+
var scope = DcpScope.Builder.newInstance().id("s1").value("v").profile("p").build();
92+
when(store.query(any())).thenReturn(StoreResult.success(List.of(scope)));
93+
94+
var impl = new DcpScopeRegistryImpl(transactionContext, store);
95+
var res = impl.create(scope);
96+
97+
assertThat(res).isFailed().detail().contains("already exists");
98+
verify(store, never()).save(any());
99+
}
100+
101+
@Test
102+
void update_should_save_when_scope_exists() {
103+
var scope = DcpScope.Builder.newInstance().id("s1").value("v").profile("p").build();
104+
when(store.query(any())).thenReturn(StoreResult.success(List.of(scope)));
105+
when(store.save(scope)).thenReturn(StoreResult.success());
106+
107+
var impl = new DcpScopeRegistryImpl(transactionContext, store);
108+
var res = impl.update(scope);
109+
110+
assertThat(res).isSucceeded();
111+
verify(store).save(scope);
112+
}
113+
114+
@Test
115+
void update_should_return_not_found_when_scope_does_not_exist() {
116+
var scope = DcpScope.Builder.newInstance().id("s1").value("v").profile("p").build();
117+
when(store.query(any())).thenReturn(StoreResult.success(List.of()));
118+
119+
var impl = new DcpScopeRegistryImpl(transactionContext, store);
120+
var res = impl.update(scope);
121+
122+
assertThat(res).isFailed().detail().contains("does not exist");
123+
verify(store, never()).save(any());
124+
}
125+
126+
@Test
127+
void query_should_return_list_from_store() {
128+
var s1 = DcpScope.Builder.newInstance().id("s1").value("v1").profile("p").build();
129+
var expected = List.of(s1);
130+
when(store.query(any())).thenReturn(StoreResult.success(expected));
131+
132+
var impl = new DcpScopeRegistryImpl(transactionContext, store);
133+
var res = impl.query(QuerySpec.max());
134+
135+
assertThat(res).isSucceeded().isEqualTo(expected);
136+
verify(store).query(QuerySpec.max());
137+
}
138+
74139
@Test
75140
void remove_should_delegate_to_store_and_return_success() {
76141
when(store.delete("id")).thenReturn(StoreResult.success());

0 commit comments

Comments
 (0)