diff --git a/src/main/java/no/ndla/taxonomy/config/AsyncConfig.java b/src/main/java/no/ndla/taxonomy/config/AsyncConfig.java index 3465909b..8363c32c 100644 --- a/src/main/java/no/ndla/taxonomy/config/AsyncConfig.java +++ b/src/main/java/no/ndla/taxonomy/config/AsyncConfig.java @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.EnableAsync; -@Profile("!default") +@Profile("!(default|junit)") @EnableAsync @Configuration public class AsyncConfig {} diff --git a/src/main/java/no/ndla/taxonomy/repositories/NodeRepository.java b/src/main/java/no/ndla/taxonomy/repositories/NodeRepository.java index 2419929a..609b7fbb 100644 --- a/src/main/java/no/ndla/taxonomy/repositories/NodeRepository.java +++ b/src/main/java/no/ndla/taxonomy/repositories/NodeRepository.java @@ -105,6 +105,18 @@ List findIdsFiltered( """) List findProgrammes(); + @Query( + """ + SELECT DISTINCT n FROM Node n + LEFT JOIN FETCH n.resourceResourceTypes rrt + LEFT JOIN FETCH rrt.resourceType + LEFT JOIN FETCH n.parentConnections pc + LEFT JOIN FETCH n.childConnections cc + WHERE n.nodeType = "SUBJECT" + AND n.context = true + """) + List findRootSubjects(); + @Query( """ SELECT DISTINCT n FROM Node n diff --git a/src/main/java/no/ndla/taxonomy/rest/v1/commands/VersionPostPut.java b/src/main/java/no/ndla/taxonomy/rest/v1/commands/VersionPostPut.java index 9043a3af..582d62fd 100644 --- a/src/main/java/no/ndla/taxonomy/rest/v1/commands/VersionPostPut.java +++ b/src/main/java/no/ndla/taxonomy/rest/v1/commands/VersionPostPut.java @@ -18,8 +18,8 @@ public class VersionPostPut implements UpdatableDto { @JsonProperty @Schema( description = - "If specified, set the id to this value. Must start with urn:subject: and be a valid URI. If omitted, an id will be assigned automatically.", - example = "urn:subject:1") + "If specified, set the id to this value. Must start with urn:version: and be a valid URI. If ommitted, an id will be assigned automatically.", + example = "urn:version:1") public Optional id = Optional.empty(); @JsonProperty diff --git a/src/main/java/no/ndla/taxonomy/service/NodeConnectionService.java b/src/main/java/no/ndla/taxonomy/service/NodeConnectionService.java index 5fb22df3..9bed2f1d 100644 --- a/src/main/java/no/ndla/taxonomy/service/NodeConnectionService.java +++ b/src/main/java/no/ndla/taxonomy/service/NodeConnectionService.java @@ -10,6 +10,7 @@ import java.net.URI; import java.util.Collection; import java.util.Optional; +import no.ndla.taxonomy.domain.DomainEntity; import no.ndla.taxonomy.domain.Node; import no.ndla.taxonomy.domain.NodeConnection; import no.ndla.taxonomy.domain.Relevance; @@ -34,4 +35,6 @@ void updateParentChild( Collection getChildConnections(Node entity); void disconnectAllChildren(Node entity); + + Optional disconnectAllInvisibleNodes(); } diff --git a/src/main/java/no/ndla/taxonomy/service/NodeConnectionServiceImpl.java b/src/main/java/no/ndla/taxonomy/service/NodeConnectionServiceImpl.java index 4f212806..9473b825 100644 --- a/src/main/java/no/ndla/taxonomy/service/NodeConnectionServiceImpl.java +++ b/src/main/java/no/ndla/taxonomy/service/NodeConnectionServiceImpl.java @@ -10,10 +10,7 @@ import java.net.URI; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import no.ndla.taxonomy.domain.Node; -import no.ndla.taxonomy.domain.NodeConnection; -import no.ndla.taxonomy.domain.NodeType; -import no.ndla.taxonomy.domain.Relevance; +import no.ndla.taxonomy.domain.*; import no.ndla.taxonomy.repositories.NodeConnectionRepository; import no.ndla.taxonomy.repositories.NodeRepository; import no.ndla.taxonomy.rest.NotFoundHttpResponseException; @@ -26,6 +23,7 @@ @Transactional(propagation = Propagation.MANDATORY) @Service public class NodeConnectionServiceImpl implements NodeConnectionService { + private final NodeConnectionRepository nodeConnectionRepository; private final ContextUpdaterService contextUpdaterService; private final NodeRepository nodeRepository; @@ -295,4 +293,24 @@ public void disconnectAllParents(URI nodeId) { public void disconnectAllChildren(Node entity) { Set.copyOf(entity.getChildConnections()).forEach(this::disconnectParentChildConnection); } + + @Transactional + @Override + public Optional disconnectAllInvisibleNodes() { + nodeRepository.findRootSubjects().forEach(subject -> { + disconnectInvisibleConnections(subject); + nodeRepository.save(subject); + }); + return Optional.empty(); + } + + private void disconnectInvisibleConnections(Node node) { + if (!node.isVisible()) { + node.getParentConnections().forEach(this::disconnectParentChildConnection); + } else { + node.getChildConnections() + .forEach(nodeConnection -> + nodeConnection.getChild().ifPresent(this::disconnectInvisibleConnections)); + } + } } diff --git a/src/main/java/no/ndla/taxonomy/service/VersionService.java b/src/main/java/no/ndla/taxonomy/service/VersionService.java index f36616cc..666a56f5 100644 --- a/src/main/java/no/ndla/taxonomy/service/VersionService.java +++ b/src/main/java/no/ndla/taxonomy/service/VersionService.java @@ -13,6 +13,8 @@ import java.time.Instant; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; import no.ndla.taxonomy.domain.Version; import no.ndla.taxonomy.domain.VersionType; @@ -20,9 +22,11 @@ import no.ndla.taxonomy.rest.v1.commands.VersionPostPut; import no.ndla.taxonomy.service.dtos.VersionDTO; import no.ndla.taxonomy.service.exceptions.NotFoundServiceException; +import no.ndla.taxonomy.service.task.Deleter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,16 +34,22 @@ @Service public class VersionService { final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final VersionRepository versionRepository; private final EntityManager entityManager; + private final VersionRepository versionRepository; + private final NodeConnectionService nodeConnectionService; private final URNValidator validator = new URNValidator(); + private final ExecutorService executor = Executors.newSingleThreadExecutor(); @Value("${spring.datasource.hikari.schema:taxonomy_api}") private String defaultSchema; - public VersionService(VersionRepository versionRepository, EntityManager entityManager) { - this.versionRepository = versionRepository; + public VersionService( + EntityManager entityManager, + VersionRepository versionRepository, + NodeConnectionService nodeConnectionService) { this.entityManager = entityManager; + this.versionRepository = versionRepository; + this.nodeConnectionService = nodeConnectionService; } @Transactional @@ -70,19 +80,35 @@ public List getVersionsOfType(VersionType versionType) { } @Transactional + @Async public void publishBetaAndArchiveCurrent(URI id) { Optional published = versionRepository.findFirstByVersionType(VersionType.PUBLISHED); if (published.isPresent()) { Version version = published.get(); version.setVersionType(VersionType.ARCHIVED); version.setArchived(Instant.now()); - versionRepository.save(version); + versionRepository.saveAndFlush(version); } Version beta = versionRepository.getByPublicId(id); beta.setVersionType(VersionType.PUBLISHED); beta.setLocked(true); beta.setPublished(Instant.now()); - versionRepository.save(beta); + versionRepository.saveAndFlush(beta); + + disconnectAllInvisibleNodes(beta.getHash()); + } + + private void disconnectAllInvisibleNodes(String hash) { + // Use a task to run in a separate thread against a specified schema + // Do not care about the result so no need to wait for it + try { + Deleter deleter = new Deleter(); + deleter.setNodeConnectionService(nodeConnectionService); + deleter.setVersion(schemaFromHash(hash)); + executor.submit(deleter); + } catch (Exception e) { + logger.info(e.getMessage(), e); + } } @Transactional diff --git a/src/main/java/no/ndla/taxonomy/service/task/Deleter.java b/src/main/java/no/ndla/taxonomy/service/task/Deleter.java new file mode 100644 index 00000000..73586124 --- /dev/null +++ b/src/main/java/no/ndla/taxonomy/service/task/Deleter.java @@ -0,0 +1,25 @@ +/* + * Part of NDLA taxonomy-api + * Copyright (C) 2024 NDLA + * + * See LICENSE + */ + +package no.ndla.taxonomy.service.task; + +import java.util.Optional; +import no.ndla.taxonomy.domain.DomainEntity; +import no.ndla.taxonomy.service.NodeConnectionService; + +public class Deleter extends Task { + private NodeConnectionService nodeConnectionService; + + public void setNodeConnectionService(NodeConnectionService nodeService) { + this.nodeConnectionService = nodeService; + } + + @Override + protected Optional execute() { + return nodeConnectionService.disconnectAllInvisibleNodes(); + } +} diff --git a/src/test/java/no/ndla/taxonomy/rest/v1/RestTest.java b/src/test/java/no/ndla/taxonomy/rest/v1/RestTest.java index 6cdeb0e1..e8edf507 100644 --- a/src/test/java/no/ndla/taxonomy/rest/v1/RestTest.java +++ b/src/test/java/no/ndla/taxonomy/rest/v1/RestTest.java @@ -25,7 +25,7 @@ @SpringBootTest @ActiveProfiles("junit") @Transactional -public abstract class RestTest extends AbstractIntegrationTest { +public class RestTest extends AbstractIntegrationTest { @Autowired EntityManager entityManager; diff --git a/src/test/java/no/ndla/taxonomy/rest/v1/VersionsTest.java b/src/test/java/no/ndla/taxonomy/rest/v1/VersionsTest.java index ad4f520b..0f36c385 100644 --- a/src/test/java/no/ndla/taxonomy/rest/v1/VersionsTest.java +++ b/src/test/java/no/ndla/taxonomy/rest/v1/VersionsTest.java @@ -179,10 +179,17 @@ public void can_update_version() throws Exception { @Test public void can_publish_version() throws Exception { - Version version = builder.version(); // BETA - testUtils.updateResource("/v1/versions/" + version.getPublicId() + "/publish", null); + var versionUri = URI.create("urn:version:beta"); + final var createVersionCommand = new VersionPostPut() { + { + id = Optional.of(versionUri); + name = "Beta"; + } + }; + testUtils.createResource("/v1/versions", createVersionCommand); + testUtils.updateResource("/v1/versions/" + versionUri + "/publish", null); - Version updated = versionRepository.getByPublicId(version.getPublicId()); + Version updated = versionRepository.getByPublicId(versionUri); assertEquals(VersionType.PUBLISHED, updated.getVersionType()); assertTrue(updated.isLocked()); assertNotNull(updated.getPublished()); diff --git a/src/test/java/no/ndla/taxonomy/service/AbstractIntegrationTest.java b/src/test/java/no/ndla/taxonomy/service/AbstractIntegrationTest.java index 294e2d0d..ab02dd05 100644 --- a/src/test/java/no/ndla/taxonomy/service/AbstractIntegrationTest.java +++ b/src/test/java/no/ndla/taxonomy/service/AbstractIntegrationTest.java @@ -13,18 +13,15 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @Testcontainers @DirtiesContext public class AbstractIntegrationTest { - static final PostgreSQLContainer postgresDB; - - static { - postgresDB = new PostgreSQLContainer<>("postgres:16.3"); - postgresDB.start(); - } + @Container + private static final PostgreSQLContainer postgresDB = new PostgreSQLContainer<>("postgres:16.3"); @Autowired EntityManager entityManager;