Skip to content

Commit b0dce22

Browse files
authored
feat: implements credential definition vertical (#555)
* feat: implements credential definition vertical * pr suggestions * chore: update issuer-admin-api-version * pr suggestions: rename schema to jsonSchema + adds jsonSchemaUrl * chore: add missing extension point
1 parent eb2fa88 commit b0dce22

File tree

41 files changed

+2551
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2551
-8
lines changed

core/issuerservice/issuerservice-core/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ plugins {
44

55
dependencies {
66
api(project(":spi:issuerservice:issuerservice-participant-spi"))
7+
api(project(":spi:issuerservice:issuerservice-credential-definition-spi"))
78

89
implementation(project(":core:lib:common-lib"))
910
implementation(libs.edc.lib.store)
1011
testImplementation(libs.edc.junit)
1112
testImplementation(testFixtures(project(":spi:issuerservice:issuerservice-participant-spi")))
13+
testImplementation(testFixtures(project(":spi:issuerservice:issuerservice-credential-definition-spi")))
1214

1315
}

core/issuerservice/issuerservice-core/src/main/java/org/eclipse/edc/issuerservice/defaults/DefaultServiceExtension.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
package org.eclipse.edc.issuerservice.defaults;
1616

17+
import org.eclipse.edc.identityhub.spi.issuance.credentials.attestation.AttestationDefinitionStore;
18+
import org.eclipse.edc.issuerservice.defaults.store.InMemoryAttestationDefinitionStore;
19+
import org.eclipse.edc.issuerservice.defaults.store.InMemoryCredentialDefinitionStore;
1720
import org.eclipse.edc.issuerservice.defaults.store.InMemoryParticipantStore;
21+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStore;
1822
import org.eclipse.edc.issuerservice.spi.participant.store.ParticipantStore;
1923
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
2024
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
@@ -31,4 +35,14 @@ public ParticipantStore createInMemoryParticipantStore() {
3135
return new InMemoryParticipantStore();
3236
}
3337

38+
39+
@Provider(isDefault = true)
40+
public AttestationDefinitionStore createInMemoryAttestationStore() {
41+
return new InMemoryAttestationDefinitionStore();
42+
}
43+
44+
@Provider(isDefault = true)
45+
public CredentialDefinitionStore createInMemoryCredentialDefinitionStore() {
46+
return new InMemoryCredentialDefinitionStore();
47+
}
3448
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
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+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.defaults.store;
16+
17+
import org.eclipse.edc.identityhub.spi.issuance.credentials.attestation.AttestationDefinitionStore;
18+
import org.eclipse.edc.identityhub.spi.issuance.credentials.model.AttestationDefinition;
19+
import org.eclipse.edc.identityhub.store.InMemoryEntityStore;
20+
import org.eclipse.edc.spi.query.QueryResolver;
21+
import org.eclipse.edc.spi.result.StoreResult;
22+
import org.eclipse.edc.store.ReflectionBasedQueryResolver;
23+
import org.jetbrains.annotations.Nullable;
24+
25+
public class InMemoryAttestationDefinitionStore extends InMemoryEntityStore<AttestationDefinition> implements AttestationDefinitionStore {
26+
27+
@Override
28+
public @Nullable AttestationDefinition resolveDefinition(String id) {
29+
return store.get(id);
30+
}
31+
32+
@Override
33+
public StoreResult<Void> delete(String id) {
34+
return super.deleteById(id);
35+
}
36+
37+
@Override
38+
protected String getId(AttestationDefinition newObject) {
39+
return newObject.id();
40+
}
41+
42+
@Override
43+
protected QueryResolver<AttestationDefinition> createQueryResolver() {
44+
return new ReflectionBasedQueryResolver<>(AttestationDefinition.class, criterionOperatorRegistry);
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
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+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.defaults.store;
16+
17+
import org.eclipse.edc.identityhub.spi.issuance.credentials.model.CredentialDefinition;
18+
import org.eclipse.edc.identityhub.store.InMemoryEntityStore;
19+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStore;
20+
import org.eclipse.edc.spi.query.QueryResolver;
21+
import org.eclipse.edc.spi.result.StoreResult;
22+
import org.eclipse.edc.store.ReflectionBasedQueryResolver;
23+
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.Optional;
27+
28+
import static org.eclipse.edc.spi.result.StoreResult.alreadyExists;
29+
import static org.eclipse.edc.spi.result.StoreResult.notFound;
30+
import static org.eclipse.edc.spi.result.StoreResult.success;
31+
32+
public class InMemoryCredentialDefinitionStore extends InMemoryEntityStore<CredentialDefinition> implements CredentialDefinitionStore {
33+
34+
35+
private final Map<String, String> credentialTypes = new HashMap<>();
36+
37+
@Override
38+
public StoreResult<Void> create(CredentialDefinition credentialDefinition) {
39+
lock.writeLock().lock();
40+
try {
41+
if (credentialTypes.containsKey(credentialDefinition.getCredentialType())) {
42+
return alreadyExists(alreadyExistsForTypeErrorMessage(credentialDefinition.getCredentialType()));
43+
}
44+
if (store.containsKey(credentialDefinition.getId())) {
45+
return alreadyExists(alreadyExistsErrorMessage(credentialDefinition.getId()));
46+
}
47+
store.put(credentialDefinition.getId(), credentialDefinition);
48+
credentialTypes.put(credentialDefinition.getCredentialType(), credentialDefinition.getId());
49+
return success(null);
50+
} finally {
51+
lock.writeLock().unlock();
52+
}
53+
}
54+
55+
@Override
56+
public StoreResult<Void> update(CredentialDefinition credentialDefinition) {
57+
lock.writeLock().lock();
58+
try {
59+
if (!store.containsKey(credentialDefinition.getId())) {
60+
return notFound(notFoundErrorMessage(credentialDefinition.getId()));
61+
}
62+
var credentialId = credentialTypes.get(credentialDefinition.getCredentialType());
63+
if (credentialId != null && !credentialId.equals(credentialDefinition.getId())) {
64+
return alreadyExists(alreadyExistsForTypeErrorMessage(credentialDefinition.getCredentialType()));
65+
}
66+
var oldDefinition = store.put(credentialDefinition.getId(), credentialDefinition);
67+
68+
Optional.ofNullable(oldDefinition)
69+
.map(CredentialDefinition::getCredentialType)
70+
.ifPresent(credentialTypes::remove);
71+
72+
return success();
73+
} finally {
74+
lock.writeLock().unlock();
75+
}
76+
}
77+
78+
@Override
79+
public StoreResult<Void> deleteById(String id) {
80+
lock.writeLock().lock();
81+
try {
82+
if (!store.containsKey(id)) {
83+
return notFound(notFoundErrorMessage(id));
84+
}
85+
var credential = store.remove(id);
86+
credentialTypes.remove(credential.getCredentialType());
87+
return success();
88+
} finally {
89+
lock.writeLock().unlock();
90+
}
91+
}
92+
93+
@Override
94+
protected String getId(CredentialDefinition newObject) {
95+
return newObject.getId();
96+
}
97+
98+
@Override
99+
protected QueryResolver<CredentialDefinition> createQueryResolver() {
100+
return new ReflectionBasedQueryResolver<>(CredentialDefinition.class, criterionOperatorRegistry);
101+
}
102+
}

core/issuerservice/issuerservice-core/src/test/java/org/eclipse/edc/issuerservice/defaults/DefaultServiceExtensionTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package org.eclipse.edc.issuerservice.defaults;
1616

17+
import org.eclipse.edc.issuerservice.defaults.store.InMemoryAttestationDefinitionStore;
18+
import org.eclipse.edc.issuerservice.defaults.store.InMemoryCredentialDefinitionStore;
1719
import org.eclipse.edc.issuerservice.defaults.store.InMemoryParticipantStore;
1820
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
1921
import org.junit.jupiter.api.Test;
@@ -26,6 +28,8 @@ class DefaultServiceExtensionTest {
2628
@Test
2729
void verifyDefaultServices(DefaultServiceExtension extension) {
2830
assertThat(extension.createInMemoryParticipantStore()).isInstanceOf(InMemoryParticipantStore.class);
31+
assertThat(extension.createInMemoryCredentialDefinitionStore()).isInstanceOf(InMemoryCredentialDefinitionStore.class);
32+
assertThat(extension.createInMemoryAttestationStore()).isInstanceOf(InMemoryAttestationDefinitionStore.class);
2933
}
3034

3135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
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+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.defaults.store;
16+
17+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStore;
18+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStoreTestBase;
19+
20+
public class InMemoryCredentialDefinitionStoreTest extends CredentialDefinitionStoreTestBase {
21+
22+
private final InMemoryCredentialDefinitionStore store = new InMemoryCredentialDefinitionStore();
23+
24+
@Override
25+
protected CredentialDefinitionStore getStore() {
26+
return store;
27+
}
28+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
plugins {
2+
`java-library`
3+
}
4+
5+
dependencies {
6+
api(project(":spi:issuerservice:issuerservice-credential-definition-spi"))
7+
api(project(":spi:issuance-credentials-spi"))
8+
9+
implementation(libs.edc.spi.transaction)
10+
implementation(libs.edc.lib.store)
11+
testImplementation(libs.edc.junit)
12+
testImplementation(testFixtures(project(":spi:issuerservice:issuerservice-credential-definition-spi")))
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
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+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.credentialdefinition;
16+
17+
import org.eclipse.edc.identityhub.spi.issuance.credentials.attestation.AttestationDefinitionStore;
18+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.CredentialDefinitionService;
19+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStore;
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.transaction.spi.TransactionContext;
25+
26+
import static org.eclipse.edc.issuerservice.credentialdefinition.CredentialDefinitionServiceExtension.NAME;
27+
28+
@Extension(value = NAME)
29+
public class CredentialDefinitionServiceExtension implements ServiceExtension {
30+
public static final String NAME = "IssuerService Credential Definition Service Extension";
31+
@Inject
32+
private TransactionContext transactionContext;
33+
@Inject
34+
private CredentialDefinitionStore store;
35+
36+
@Inject
37+
private AttestationDefinitionStore attestationDefinitionStore;
38+
39+
@Provider
40+
public CredentialDefinitionService createParticipantService() {
41+
return new CredentialDefinitionServiceImpl(transactionContext, store, attestationDefinitionStore);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
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+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.issuerservice.credentialdefinition;
16+
17+
import org.eclipse.edc.identityhub.spi.issuance.credentials.attestation.AttestationDefinitionStore;
18+
import org.eclipse.edc.identityhub.spi.issuance.credentials.model.AttestationDefinition;
19+
import org.eclipse.edc.identityhub.spi.issuance.credentials.model.CredentialDefinition;
20+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.CredentialDefinitionService;
21+
import org.eclipse.edc.issuerservice.spi.credentialdefinition.store.CredentialDefinitionStore;
22+
import org.eclipse.edc.spi.query.Criterion;
23+
import org.eclipse.edc.spi.query.QuerySpec;
24+
import org.eclipse.edc.spi.result.ServiceResult;
25+
import org.eclipse.edc.transaction.spi.TransactionContext;
26+
27+
import java.util.Collection;
28+
import java.util.stream.Collectors;
29+
30+
import static org.eclipse.edc.spi.result.ServiceResult.from;
31+
32+
public class CredentialDefinitionServiceImpl implements CredentialDefinitionService {
33+
private final TransactionContext transactionContext;
34+
private final CredentialDefinitionStore credentialDefinitionStore;
35+
private final AttestationDefinitionStore attestationDefinitionStore;
36+
37+
public CredentialDefinitionServiceImpl(TransactionContext transactionContext, CredentialDefinitionStore credentialDefinitionStore, AttestationDefinitionStore attestationDefinitionStore) {
38+
this.transactionContext = transactionContext;
39+
this.credentialDefinitionStore = credentialDefinitionStore;
40+
this.attestationDefinitionStore = attestationDefinitionStore;
41+
}
42+
43+
@Override
44+
public ServiceResult<Void> createCredentialDefinition(CredentialDefinition credentialDefinition) {
45+
return transactionContext.execute(() -> internalCreate(credentialDefinition));
46+
47+
}
48+
49+
@Override
50+
public ServiceResult<Void> deleteCredentialDefinition(String credentialDefinitionId) {
51+
return transactionContext.execute(() -> from(credentialDefinitionStore.deleteById(credentialDefinitionId)));
52+
}
53+
54+
@Override
55+
public ServiceResult<Void> updateCredentialDefinition(CredentialDefinition credentialDefinition) {
56+
return transactionContext.execute(() -> internalUpdate(credentialDefinition));
57+
}
58+
59+
@Override
60+
public ServiceResult<Collection<CredentialDefinition>> queryCredentialDefinitions(QuerySpec querySpec) {
61+
return transactionContext.execute(() -> from(credentialDefinitionStore.query(querySpec)));
62+
63+
}
64+
65+
@Override
66+
public ServiceResult<CredentialDefinition> findCredentialDefinitionById(String credentialDefinitionId) {
67+
return transactionContext.execute(() -> from(credentialDefinitionStore.findById(credentialDefinitionId)));
68+
69+
}
70+
71+
private ServiceResult<Void> internalCreate(CredentialDefinition credentialDefinition) {
72+
return validateAttestations(credentialDefinition)
73+
.compose(u -> from(credentialDefinitionStore.create(credentialDefinition)));
74+
}
75+
76+
private ServiceResult<Void> internalUpdate(CredentialDefinition credentialDefinition) {
77+
return validateAttestations(credentialDefinition)
78+
.compose(u -> from(credentialDefinitionStore.update(credentialDefinition)));
79+
}
80+
81+
private ServiceResult<Void> validateAttestations(CredentialDefinition credentialDefinition) {
82+
var query = QuerySpec.Builder.newInstance()
83+
.filter(Criterion.criterion("id", "in", credentialDefinition.getAttestations()))
84+
.build();
85+
return from(attestationDefinitionStore.query(query))
86+
.compose(attestationDefinitions -> checkAttestations(credentialDefinition, attestationDefinitions));
87+
}
88+
89+
private ServiceResult<Void> checkAttestations(CredentialDefinition credentialDefinition, Collection<AttestationDefinition> attestationDefinitions) {
90+
if (attestationDefinitions.size() != credentialDefinition.getAttestations().size()) {
91+
92+
var attestationsIds = attestationDefinitions.stream().map(AttestationDefinition::id).collect(Collectors.toSet());
93+
94+
var missingAttestations = credentialDefinition.getAttestations().stream()
95+
.filter(attestationId -> !attestationsIds.contains(attestationId))
96+
.collect(Collectors.toSet());
97+
98+
return ServiceResult.badRequest("Attestation definitions [%s] not found".formatted(String.join(",", missingAttestations)));
99+
}
100+
return ServiceResult.success();
101+
}
102+
}

0 commit comments

Comments
 (0)