diff --git a/DEPENDENCIES b/DEPENDENCIES index ed5ff40c..ec050b5f 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -218,6 +218,7 @@ maven/mavencentral/org.eclipse.edc/json-lib/0.8.2-SNAPSHOT, Apache-2.0, approved maven/mavencentral/org.eclipse.edc/junit-base/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/junit/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/jws2020-lib/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jwt-signer-spi/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/jwt-spi/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/jwt-verifiable-credentials/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/keys-lib/0.8.2-SNAPSHOT, Apache-2.0, approved, technology.edc @@ -314,12 +315,18 @@ maven/mavencentral/org.jetbrains/annotations/17.0.0, Apache-2.0, approved, clear maven/mavencentral/org.jetbrains/annotations/24.1.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.junit-pioneer/junit-pioneer/2.2.0, EPL-2.0, approved, #11857 maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.3, EPL-2.0, approved, #9714 +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.11.0, EPL-2.0, approved, #15935 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.3, EPL-2.0, approved, #9711 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.11.0, EPL-2.0, approved, #15939 maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.3, EPL-2.0, approved, #15250 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.11.0, EPL-2.0, approved, #15940 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.3, EPL-2.0, approved, #9715 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.11.0, EPL-2.0, approved, #15936 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.3, EPL-2.0, approved, #9709 -maven/mavencentral/org.junit.platform/junit-platform-launcher/1.10.3, EPL-2.0, approved, #15216 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.11.0, EPL-2.0, approved, #15932 +maven/mavencentral/org.junit.platform/junit-platform-launcher/1.11.0, EPL-2.0, approved, #15934 maven/mavencentral/org.junit/junit-bom/5.10.3, EPL-2.0, approved, #9844 +maven/mavencentral/org.junit/junit-bom/5.11.0, , restricted, clearlydefined maven/mavencentral/org.junit/junit-bom/5.9.2, EPL-2.0, approved, #4711 maven/mavencentral/org.jvnet.mimepull/mimepull/1.9.15, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ21484 maven/mavencentral/org.mockito/mockito-core/5.12.0, MIT AND (Apache-2.0 AND MIT) AND Apache-2.0, approved, #14678 diff --git a/core/federated-catalog-core/build.gradle.kts b/core/federated-catalog-core/build.gradle.kts index 6d2b9b4a..1a0cd632 100644 --- a/core/federated-catalog-core/build.gradle.kts +++ b/core/federated-catalog-core/build.gradle.kts @@ -44,4 +44,5 @@ dependencies { testImplementation(libs.awaitility) testImplementation(testFixtures(project(":spi:federated-catalog-spi"))) + testImplementation(testFixtures(project(":spi:crawler-spi"))) } diff --git a/core/federated-catalog-core/src/main/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectory.java b/core/federated-catalog-core/src/main/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectory.java index 329eba50..f660b126 100644 --- a/core/federated-catalog-core/src/main/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectory.java +++ b/core/federated-catalog-core/src/main/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectory.java @@ -18,18 +18,19 @@ import org.eclipse.edc.crawler.spi.TargetNodeDirectory; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class InMemoryNodeDirectory implements TargetNodeDirectory { - private final List cache = new CopyOnWriteArrayList<>(); + private final Map cache = new ConcurrentHashMap<>(); @Override public List getAll() { - return List.copyOf(cache); //never return the internal copy + return List.copyOf(cache.values()); //never return the internal copy } @Override public void insert(TargetNode node) { - cache.add(node); + cache.put(node.id(), node); } } diff --git a/core/federated-catalog-core/src/test/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectoryTest.java b/core/federated-catalog-core/src/test/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectoryTest.java new file mode 100644 index 00000000..202e91b1 --- /dev/null +++ b/core/federated-catalog-core/src/test/java/org/eclipse/edc/catalog/directory/InMemoryNodeDirectoryTest.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.directory; + +import org.eclipse.edc.catalog.spi.testfixtures.TargetNodeDirectoryTestBase; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; + +class InMemoryNodeDirectoryTest extends TargetNodeDirectoryTestBase { + + private final InMemoryNodeDirectory store = new InMemoryNodeDirectory(); + + @Override + protected TargetNodeDirectory getStore() { + return store; + } + +} \ No newline at end of file diff --git a/extensions/store/sql/target-node-directory-sql/build.gradle.kts b/extensions/store/sql/target-node-directory-sql/build.gradle.kts new file mode 100644 index 00000000..0820ee74 --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + api(project(":spi:crawler-spi")) + implementation(libs.edc.sql.core) // for the SqlStatements + implementation(libs.edc.sql.bootstrapper) + implementation(libs.edc.spi.transaction.datasource) + implementation(libs.edc.lib.util) + + testImplementation(libs.edc.junit) + testImplementation(testFixtures(libs.edc.sql.core)) + testImplementation(testFixtures(project(":spi:crawler-spi"))) +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/BaseSqlDialectStatements.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/BaseSqlDialectStatements.java new file mode 100644 index 00000000..3d6e9dc6 --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/BaseSqlDialectStatements.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import org.eclipse.edc.catalog.store.sql.schema.postgres.TargetNodeMapping; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.sql.translation.PostgresqlOperatorTranslator; +import org.eclipse.edc.sql.translation.SqlQueryStatement; + +import static java.lang.String.format; + +public abstract class BaseSqlDialectStatements implements TargetNodeStatements { + + @Override + public String getFindByIdTemplate() { + return format("SELECT * FROM %s WHERE %s = ?", getTargetNodeDirectoryTable(), getIdColumn()); + } + + @Override + public String getUpdateTemplate() { + return executeStatement() + .column(getNameColumn()) + .column(getTargetUrlColumn()) + .jsonColumn(getSupportedProtocolsColumn()) + .update(getTargetNodeDirectoryTable(), getIdColumn()); + } + + @Override + public String getInsertTemplate() { + return executeStatement() + .column(getIdColumn()) + .column(getNameColumn()) + .column(getTargetUrlColumn()) + .jsonColumn(getSupportedProtocolsColumn()) + .insertInto(getTargetNodeDirectoryTable()); + } + + @Override + public SqlQueryStatement createQuery(QuerySpec querySpec) { + var select = getSelectStatement(); + return new SqlQueryStatement(select, querySpec, new TargetNodeMapping(this), new PostgresqlOperatorTranslator()); + } + + @Override + public String getSelectStatement() { + return format("SELECT * FROM %s", getTargetNodeDirectoryTable()); + } +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectory.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectory.java new file mode 100644 index 00000000..64427cc6 --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectory.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.crawler.spi.TargetNode; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.sql.QueryExecutor; +import org.eclipse.edc.sql.store.AbstractSqlStore; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; +import org.eclipse.edc.transaction.spi.TransactionContext; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class SqlTargetNodeDirectory extends AbstractSqlStore implements TargetNodeDirectory { + + private final TargetNodeStatements statements; + + public SqlTargetNodeDirectory(DataSourceRegistry dataSourceRegistry, String dataSourceName, TransactionContext transactionContext, + ObjectMapper objectMapper, QueryExecutor queryExecutor, TargetNodeStatements statements) { + super(dataSourceRegistry, dataSourceName, transactionContext, objectMapper, queryExecutor); + this.statements = statements; + } + + @Override + public List getAll() { + return transactionContext.execute(() -> { + try (var connection = getConnection()) { + var query = statements.createQuery(QuerySpec.max()); + return queryExecutor.query(connection, true, this::mapResultSet, query.getQueryAsString(), query.getParameters()).toList(); + } catch (SQLException e) { + throw new EdcPersistenceException(e); + } + }); + } + + @Override + public void insert(TargetNode node) { + transactionContext.execute(() -> { + try (var connection = getConnection()) { + var id = node.id(); + + if (findByIdInternal(connection, id) == null) { + insertInternal(connection, id, node); + } else { + updateInternal(connection, id, node); + } + + } catch (SQLException e) { + throw new EdcPersistenceException(e); + } + }); + } + + private TargetNode findByIdInternal(Connection connection, String id) { + var stmt = statements.getFindByIdTemplate(); + return queryExecutor.single(connection, false, this::mapResultSet, stmt, id); + } + + private void insertInternal(Connection connection, String id, TargetNode targetNode) { + var stmt = statements.getInsertTemplate(); + queryExecutor.execute(connection, + stmt, + id, + targetNode.name(), + targetNode.targetUrl(), + toJson(targetNode.supportedProtocols()) + ); + } + + private void updateInternal(Connection connection, String id, TargetNode targetNode) { + var stmt = statements.getUpdateTemplate(); + queryExecutor.execute(connection, + stmt, + targetNode.name(), + targetNode.targetUrl(), + toJson(targetNode.supportedProtocols()), + id + ); + } + + private TargetNode mapResultSet(ResultSet resultSet) throws Exception { + return new TargetNode( + resultSet.getString(statements.getNameColumn()), + resultSet.getString(statements.getIdColumn()), + resultSet.getString(statements.getTargetUrlColumn()), + fromJson(resultSet.getString(statements.getSupportedProtocolsColumn()), new TypeReference<>() { + }) + ); + } + +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtension.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtension.java new file mode 100644 index 00000000..776b03f1 --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtension.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import org.eclipse.edc.catalog.store.sql.schema.postgres.PostgresDialectStatements; +import org.eclipse.edc.crawler.spi.TargetNode; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.sql.QueryExecutor; +import org.eclipse.edc.sql.bootstrapper.SqlSchemaBootstrapper; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; +import org.eclipse.edc.transaction.spi.TransactionContext; + +@Provides(TargetNodeDirectory.class) +@Extension(value = "SQL target node directory") +public class SqlTargetNodeDirectoryExtension implements ServiceExtension { + + @Setting(value = "The datasource to be used", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) + public static final String DATASOURCE_NAME = "edc.sql.store.targetnodedirectory.datasource"; + + @Inject + private DataSourceRegistry dataSourceRegistry; + @Inject + private TransactionContext trxContext; + @Inject(required = false) + private TargetNodeStatements statements; + @Inject + private TypeManager typeManager; + + @Inject + private QueryExecutor queryExecutor; + + @Inject + private SqlSchemaBootstrapper sqlSchemaBootstrapper; + + @Override + public void initialize(ServiceExtensionContext context) { + typeManager.registerTypes(TargetNode.class); + var dataSourceName = context.getSetting(DATASOURCE_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); + var targetNodeDirectory = new SqlTargetNodeDirectory(dataSourceRegistry, dataSourceName, trxContext, + typeManager.getMapper(), queryExecutor, getStatementImpl()); + context.registerService(TargetNodeDirectory.class, targetNodeDirectory); + sqlSchemaBootstrapper.addStatementFromResource(dataSourceName, "target-node-directory-schema.sql"); + } + + /** + * returns an externally-provided sql statement dialect, or postgres as a default + */ + private TargetNodeStatements getStatementImpl() { + return statements != null ? statements : new PostgresDialectStatements(); + } + +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/TargetNodeStatements.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/TargetNodeStatements.java new file mode 100644 index 00000000..b62d685e --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/TargetNodeStatements.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.sql.statement.SqlStatements; +import org.eclipse.edc.sql.translation.SqlQueryStatement; + +public interface TargetNodeStatements extends SqlStatements { + + default String getTargetNodeDirectoryTable() { + return "edc_target_node_directory"; + } + + default String getIdColumn() { + return "id"; + } + + default String getNameColumn() { + return "name"; + } + + default String getTargetUrlColumn() { + return "target_url"; + } + + default String getSupportedProtocolsColumn() { + return "supported_protocols"; + } + + String getInsertTemplate(); + + String getFindByIdTemplate(); + + String getUpdateTemplate(); + + SqlQueryStatement createQuery(QuerySpec query); + + String getSelectStatement(); +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/PostgresDialectStatements.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/PostgresDialectStatements.java new file mode 100644 index 00000000..d23338b6 --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/PostgresDialectStatements.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql.schema.postgres; + +import org.eclipse.edc.catalog.store.sql.BaseSqlDialectStatements; +import org.eclipse.edc.sql.dialect.PostgresDialect; + +public class PostgresDialectStatements extends BaseSqlDialectStatements { + + @Override + public String getFormatAsJsonOperator() { + return PostgresDialect.getJsonCastOperator(); + } +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/TargetNodeMapping.java b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/TargetNodeMapping.java new file mode 100644 index 00000000..a8f4617a --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/java/org/eclipse/edc/catalog/store/sql/schema/postgres/TargetNodeMapping.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql.schema.postgres; + +import org.eclipse.edc.catalog.store.sql.TargetNodeStatements; +import org.eclipse.edc.sql.translation.JsonFieldTranslator; +import org.eclipse.edc.sql.translation.TranslationMapping; + +public class TargetNodeMapping extends TranslationMapping { + + public TargetNodeMapping(TargetNodeStatements statements) { + add("id", statements.getIdColumn()); + add("name", statements.getNameColumn()); + add("targetUrl", statements.getTargetUrlColumn()); + add("supportedProtocols", new JsonFieldTranslator(statements.getSupportedProtocolsColumn())); + } +} diff --git a/extensions/store/sql/target-node-directory-sql/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/store/sql/target-node-directory-sql/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000..9b25d36a --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Amadeus IT Group +# +# 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: +# Amadeus IT Group - initial API and implementation +# +# + +org.eclipse.edc.catalog.store.sql.SqlTargetNodeDirectoryExtension \ No newline at end of file diff --git a/extensions/store/sql/target-node-directory-sql/src/main/resources/target-node-directory-schema.sql b/extensions/store/sql/target-node-directory-sql/src/main/resources/target-node-directory-schema.sql new file mode 100644 index 00000000..d4fab3aa --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/main/resources/target-node-directory-schema.sql @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +-- only intended for and tested with Postgres! +CREATE TABLE IF NOT EXISTS edc_target_node_directory +( + id VARCHAR PRIMARY KEY NOT NULL, + name VARCHAR NOT NULL, + target_url VARCHAR NOT NULL, + supported_protocols JSON +); + +COMMENT ON COLUMN edc_target_node_directory.supported_protocols IS 'List serialized as JSON'; \ No newline at end of file diff --git a/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtensionTest.java b/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtensionTest.java new file mode 100644 index 00000000..f49210ca --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryExtensionTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.crawler.spi.TargetNode; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.catalog.store.sql.SqlTargetNodeDirectoryExtension.DATASOURCE_NAME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +public class SqlTargetNodeDirectoryExtensionTest { + + private final TypeManager typeManager = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(TypeManager.class, typeManager); + when(typeManager.getMapper()).thenReturn(new ObjectMapper()); + } + + @Test + void shouldInitializeTheStore(SqlTargetNodeDirectoryExtension extension, ServiceExtensionContext context) { + var config = mock(Config.class); + when(context.getConfig()).thenReturn(config); + when(config.getString(any(), any())).thenReturn("test"); + + extension.initialize(context); + + var service = context.getService(TargetNodeDirectory.class); + assertThat(service).isInstanceOf(SqlTargetNodeDirectory.class); + + verify(typeManager).registerTypes(TargetNode.class); + verify(config).getString(DATASOURCE_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); + } +} diff --git a/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryTest.java b/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryTest.java new file mode 100644 index 00000000..3e10317d --- /dev/null +++ b/extensions/store/sql/target-node-directory-sql/src/test/java/org/eclipse/edc/catalog/store/sql/SqlTargetNodeDirectoryTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.store.sql; + +import org.eclipse.edc.catalog.spi.testfixtures.TargetNodeDirectoryTestBase; +import org.eclipse.edc.catalog.store.sql.schema.postgres.PostgresDialectStatements; +import org.eclipse.edc.connector.controlplane.catalog.spi.Catalog; +import org.eclipse.edc.connector.controlplane.catalog.spi.Dataset; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; +import org.eclipse.edc.json.JacksonTypeManager; +import org.eclipse.edc.junit.annotations.ComponentTest; +import org.eclipse.edc.junit.testfixtures.TestUtils; +import org.eclipse.edc.sql.QueryExecutor; +import org.eclipse.edc.sql.testfixtures.PostgresqlStoreSetupExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.io.IOException; + +@ComponentTest +@ExtendWith(PostgresqlStoreSetupExtension.class) +public class SqlTargetNodeDirectoryTest extends TargetNodeDirectoryTestBase { + + private final TargetNodeStatements statements = new PostgresDialectStatements(); + + private TargetNodeDirectory store; + + @BeforeEach + void setup(PostgresqlStoreSetupExtension extension, QueryExecutor queryExecutor) throws IOException { + var typeManager = new JacksonTypeManager(); + typeManager.registerTypes(Catalog.class, Dataset.class); + store = new SqlTargetNodeDirectory(extension.getDataSourceRegistry(), extension.getDatasourceName(), + extension.getTransactionContext(), typeManager.getMapper(), queryExecutor, statements); + + var schema = TestUtils.getResourceFileContentAsString("target-node-directory-schema.sql"); + extension.runQuery(schema); + } + + @AfterEach + void tearDown(PostgresqlStoreSetupExtension extension) { + extension.runQuery("DROP TABLE " + statements.getTargetNodeDirectoryTable()); + } + + @Override + protected TargetNodeDirectory getStore() { + return store; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9917695a..5a188ecd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,7 @@ include(":core:federated-catalog-core") include(":core:common:lib:catalog-util-lib") include(":extensions:api:federated-catalog-api") +include(":extensions:store:sql:target-node-directory-sql") include(":extensions:store:sql:federated-catalog-cache-sql") include(":launchers:catalog-base") include(":launchers:catalog-dcp") diff --git a/spi/crawler-spi/build.gradle.kts b/spi/crawler-spi/build.gradle.kts index 5e1e2876..d7acecac 100644 --- a/spi/crawler-spi/build.gradle.kts +++ b/spi/crawler-spi/build.gradle.kts @@ -14,9 +14,14 @@ plugins { `java-library` + `java-test-fixtures` } dependencies { api(libs.edc.spi.catalog) api(libs.edc.spi.core) + + testFixturesImplementation(libs.edc.junit) + testFixturesImplementation(libs.junit.jupiter.api) + testFixturesImplementation(libs.assertj) } diff --git a/spi/crawler-spi/src/testFixtures/java/org/eclipse/edc/catalog/spi/testfixtures/TargetNodeDirectoryTestBase.java b/spi/crawler-spi/src/testFixtures/java/org/eclipse/edc/catalog/spi/testfixtures/TargetNodeDirectoryTestBase.java new file mode 100644 index 00000000..b32e25cd --- /dev/null +++ b/spi/crawler-spi/src/testFixtures/java/org/eclipse/edc/catalog/spi/testfixtures/TargetNodeDirectoryTestBase.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Amadeus IT Group + * + * 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: + * Amadeus IT Group - initial API and implementation + * + */ + +package org.eclipse.edc.catalog.spi.testfixtures; + +import org.eclipse.edc.crawler.spi.TargetNode; +import org.eclipse.edc.crawler.spi.TargetNodeDirectory; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class TargetNodeDirectoryTestBase { + + + protected abstract TargetNodeDirectory getStore(); + + private TargetNode createTargetNode(String id) { + return new TargetNode(UUID.randomUUID().toString(), id, "http://example.com", List.of()); + } + + @Nested + class Insert { + + @Test + void insert_notExisting_shouldInsert() { + var node = createTargetNode(UUID.randomUUID().toString()); + + getStore().insert(node); + + var result = getStore().getAll(); + + assertThat(result) + .hasSize(1) + .allSatisfy(n -> assertThat(n).usingRecursiveComparison().isEqualTo(node)); + } + + @Test + void insert_existing_shouldUpdate() { + var id = UUID.randomUUID().toString(); + var node1 = createTargetNode(id); + var node2 = createTargetNode(id); + + getStore().insert(node1); + getStore().insert(node2); + + var result = getStore().getAll(); + + assertThat(result) + .hasSize(1) + .allSatisfy(n -> assertThat(n).usingRecursiveComparison().isEqualTo(node2)); + } + } + + @Nested + class GetAll { + + @Test + void getAll() { + var nodes = List.of(createTargetNode(UUID.randomUUID().toString()), createTargetNode(UUID.randomUUID().toString())); + + nodes.forEach(getStore()::insert); + + var result = getStore().getAll(); + + assertThat(result) + .hasSize(2) + .anySatisfy(n -> assertThat(n.id()).isEqualTo(nodes.get(0).id())) + .anySatisfy(n -> assertThat(n.id()).isEqualTo(nodes.get(1).id())); + } + + } +}