Skip to content

Commit

Permalink
feat: add ParticipantContextStore (#224)
Browse files Browse the repository at this point in the history
* feat: add ParticipantContextStore (mem + sql)

* pr remarks
  • Loading branch information
paullatzelsperger authored Jan 16, 2024
1 parent ecc39dc commit b139186
Show file tree
Hide file tree
Showing 26 changed files with 1,237 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import com.apicatalog.ld.signature.SignatureSuite;
import org.eclipse.edc.identityhub.defaults.EdcScopeToCriterionTransformer;
import org.eclipse.edc.identityhub.defaults.InMemoryCredentialStore;
import org.eclipse.edc.identityhub.defaults.InMemoryParticipantContextStore;
import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer;
import org.eclipse.edc.identityhub.spi.model.IdentityHubConstants;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.identityhub.token.rules.ClaimIsPresentRule;
import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
Expand Down Expand Up @@ -64,10 +66,15 @@ public void initialize(ServiceExtensionContext context) {
}

@Provider(isDefault = true)
public CredentialStore createInMemStore() {
public CredentialStore createDefaultCredentialStore() {
return new InMemoryCredentialStore();
}

@Provider(isDefault = true)
public ParticipantContextStore createDefaultParticipantContextStore() {
return new InMemoryParticipantContextStore();
}

@Provider(isDefault = true)
public ScopeToCriterionTransformer createScopeTransformer(ServiceExtensionContext context) {
context.getMonitor().warning("Using the default EdcScopeToCriterionTransformer. This is not intended for production use and should be replaced " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,79 +18,19 @@
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource;
import org.eclipse.edc.spi.query.QueryResolver;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.StoreResult;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static org.eclipse.edc.spi.result.StoreResult.alreadyExists;
import static org.eclipse.edc.spi.result.StoreResult.notFound;
import static org.eclipse.edc.spi.result.StoreResult.success;

public class InMemoryCredentialStore implements CredentialStore {
private final Map<String, VerifiableCredentialResource> store = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private final QueryResolver<VerifiableCredentialResource> queryResolver = new ReflectionBasedQueryResolver<>(VerifiableCredentialResource.class, new CriterionToCredentialResourceConverter());

@Override
public StoreResult<Void> create(VerifiableCredentialResource credentialResource) {
lock.writeLock().lock();
var id = credentialResource.getId();
try {
if (store.containsKey(id)) {
return alreadyExists("A VerifiableCredentialResource with ID %s already exists".formatted(id));
}
store.put(id, credentialResource);
return success(null);
} finally {
lock.writeLock().unlock();
}
}

@Override
public StoreResult<Stream<VerifiableCredentialResource>> query(QuerySpec querySpec) {
lock.readLock().lock();
try {
// if no filter is present, we return true
Predicate<Object> fallback = querySpec.getFilterExpression().isEmpty() ? x -> true : x -> false;
var result = queryResolver.query(store.values().stream(), querySpec, Predicate::or, fallback);
return success(result);
} finally {
lock.readLock().unlock();
}
}
/**
* In-memory variant of the {@link CredentialStore} that is thread-safe.
*/
public class InMemoryCredentialStore extends InMemoryEntityStore<VerifiableCredentialResource> implements CredentialStore {

@Override
public StoreResult<Void> update(VerifiableCredentialResource credentialResource) {
lock.writeLock().lock();
try {
var id = credentialResource.getId();
if (!store.containsKey(id)) {
return notFound("A VerifiableCredentialResource with ID %s was not found".formatted(id));
}
store.put(id, credentialResource);
return success();
} finally {
lock.writeLock().unlock();
}
protected String getId(VerifiableCredentialResource newObject) {
return newObject.getId();
}

@Override
public StoreResult<Void> delete(String id) {
lock.writeLock().lock();
try {
if (!store.containsKey(id)) {
return notFound("A VerifiableCredentialResource with ID %s was not found".formatted(id));
}
store.remove(id);
return success();
} finally {
lock.writeLock().unlock();
}
protected QueryResolver<VerifiableCredentialResource> createQueryResolver() {
return new ReflectionBasedQueryResolver<>(VerifiableCredentialResource.class, new CriterionToCredentialResourceConverter());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.defaults;

import org.eclipse.edc.spi.query.QueryResolver;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.StoreResult;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static org.eclipse.edc.spi.result.StoreResult.alreadyExists;
import static org.eclipse.edc.spi.result.StoreResult.notFound;
import static org.eclipse.edc.spi.result.StoreResult.success;

/**
* Base class for in-mem entity stores, that implement basic CRUD operations.
*/
abstract class InMemoryEntityStore<T> {
protected final Map<String, T> store = new HashMap<>();
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
protected final QueryResolver<T> queryResolver = createQueryResolver();

/**
* Creates a new entity if none exists.
*
* @param newObject the new object to insert.
* @return failure if an object with the same ID already exists.
*/
public StoreResult<Void> create(T newObject) {
lock.writeLock().lock();
var id = getId(newObject);
try {
if (store.containsKey(id)) {
return alreadyExists("A VerifiableCredentialResource with ID %s already exists".formatted(id));
}
store.put(id, newObject);
return success(null);
} finally {
lock.writeLock().unlock();
}
}

/**
* Performs a query using the given query parameters.
*
* @param querySpec A non-null QuerySpec.
* @return A (potentially empty) Stream of objects. Callers must close the stream.
*/
public StoreResult<Stream<T>> query(QuerySpec querySpec) {
lock.readLock().lock();
try {
// if no filter is present, we return true
Predicate<Object> fallback = querySpec.getFilterExpression().isEmpty() ? x -> true : x -> false;
var result = queryResolver.query(store.values().stream(), querySpec, Predicate::or, fallback);
return success(result);
} finally {
lock.readLock().unlock();
}
}

/**
* Replaces an existing entity with a new object.
*
* @param newObject the new entity
* @return failure if an object with the same ID was not found.
*/
public StoreResult<Void> update(T newObject) {
lock.writeLock().lock();
try {
var id = getId(newObject);
if (!store.containsKey(id)) {
return notFound("A VerifiableCredentialResource with ID %s was not found".formatted(id));
}
store.put(id, newObject);
return success();
} finally {
lock.writeLock().unlock();
}
}

/**
* Deletes the object with the given ID
*
* @param id The ID of the object to delete.
* @return failure if an object with the given ID was not found.
*/
public StoreResult<Void> deleteById(String id) {
lock.writeLock().lock();
try {
if (!store.containsKey(id)) {
return notFound("A VerifiableCredentialResource with ID %s was not found".formatted(id));
}
store.remove(id);
return success();
} finally {
lock.writeLock().unlock();
}
}

protected abstract String getId(T newObject);

protected abstract QueryResolver<T> createQueryResolver();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.defaults;

import org.eclipse.edc.connector.core.store.CriterionToPredicateConverterImpl;
import org.eclipse.edc.connector.core.store.ReflectionBasedQueryResolver;
import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.spi.query.QueryResolver;

/**
* In-memory variant of the {@link ParticipantContextStore} that is thread-safe.
*/
public class InMemoryParticipantContextStore extends InMemoryEntityStore<ParticipantContext> implements ParticipantContextStore {
@Override
protected String getId(ParticipantContext newObject) {
return newObject.getParticipantId();
}

@Override
protected QueryResolver<ParticipantContext> createQueryResolver() {
return new ReflectionBasedQueryResolver<>(ParticipantContext.class, new CriterionToPredicateConverterImpl());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

package org.eclipse.edc.identityhub.defaults;

import org.eclipse.edc.identityhub.credentials.store.test.CredentialStoreTestBase;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.store.test.CredentialStoreTestBase;

class InMemoryCredentialStoreTest extends CredentialStoreTestBase {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.defaults;

import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.identityhub.store.test.ParticipantContextStoreTestBase;

class InMemoryParticipantContextStoreTest extends ParticipantContextStoreTestBase {

private final InMemoryParticipantContextStore store = new InMemoryParticipantContextStore();

@Override
protected ParticipantContextStore getStore() {
return store;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public StoreResult<Void> update(VerifiableCredentialResource credentialResource)
}

@Override
public StoreResult<Void> delete(String id) {
public StoreResult<Void> deleteById(String id) {
Objects.requireNonNull(id);
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

package org.eclipse.edc.identityhub.store.sql.credentials;

import org.eclipse.edc.identityhub.credentials.store.test.CredentialStoreTestBase;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.store.sql.credentials.schema.postgres.PostgresDialectStatements;
import org.eclipse.edc.identityhub.store.test.CredentialStoreTestBase;
import org.eclipse.edc.junit.annotations.ComponentTest;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.sql.QueryExecutor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

plugins {
`java-library`
}

dependencies {
api(project(":spi:identity-hub-store-spi"))
implementation(libs.edc.core.sql) // for the SqlStatements
implementation(libs.edc.spi.transaction.datasource)

testImplementation(testFixtures(project(":spi:identity-hub-store-spi")))
testImplementation(testFixtures(libs.edc.core.sql))
testImplementation(libs.edc.junit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

-- only intended for and tested with Postgres!
CREATE TABLE participant_context
(
participant_id VARCHAR PRIMARY KEY NOT NULL, -- ID of the ParticipantContext
created_date BIGINT NOT NULL, -- POSIX timestamp of the creation of the PC
last_modified_date BIGINT, -- POSIX timestamp of the last modified date
state INTEGER NOT NULL, -- 0 = CREATED, 1 = ACTIVE, 2 = DEACTIVATED
api_token_alias VARCHAR NOT NULL -- alias under which this PC's api token is stored in the vault
);
CREATE UNIQUE INDEX participant_context_participant_id_uindex ON participant_context USING btree (participant_id);

Loading

0 comments on commit b139186

Please sign in to comment.