diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/DossierPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/DossierPersistenceService.java index 950e4883f..f7277346d 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/DossierPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/DossierPersistenceService.java @@ -10,8 +10,8 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; -import javax.transaction.Transactional; +import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -28,6 +28,8 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.do import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -42,6 +44,7 @@ public class DossierPersistenceService { private final WatermarkService watermarkService; + @Transactional public DossierEntity insert(CreateOrUpdateDossierRequest createOrUpdateDossierRequest) { DossierEntity dossier = new DossierEntity(); @@ -55,8 +58,8 @@ public class DossierPersistenceService { dossier.setReportTemplates(reportTemplates); this.handleDossierStatus(createOrUpdateDossierRequest, dossier); this.handleWatermark(createOrUpdateDossierRequest, dossier); - return dossierRepository.save(dossier); + return dossierRepository.saveAndFlush(dossier); } diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/DossierService.java b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/DossierService.java index 9a871adb2..3ebd370d2 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/DossierService.java +++ b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/DossierService.java @@ -20,6 +20,9 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.do import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; + +import javax.validation.ConstraintViolationException; /** * Provides the internal interface between dossier request and the actual persistence. @@ -51,7 +54,16 @@ public class DossierService { throw new BadRequestException("Dossier template is not active."); } - return dossierPersistenceService.insert(createOrUpdateDossierRequest); + try { + return dossierPersistenceService.insert(createOrUpdateDossierRequest); + } catch (Exception e) { + if (e.getCause() instanceof ConstraintViolationException) { + throw new ConflictException("Dossier with this name already exists"); + } else { + log.debug("Unknown error when creating a dossier: ", e); + throw new BadRequestException("Failed to save dossier!"); + } + } } diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml b/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml index 421bfa244..89cd55cdb 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml +++ b/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml @@ -111,3 +111,5 @@ databaseChangeLog: file: db/changelog/tenant/44-add-redaction-preview-color-column.changelog.yaml - include: file: db/changelog/tenant/sql/43-add-applied-redaction-color.sql + - include: + file: db/changelog/tenant/sql/45-unique-dossier-name.sql diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/tenant/sql/45-unique-dossier-name.sql b/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/tenant/sql/45-unique-dossier-name.sql new file mode 100644 index 000000000..ed848777f --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/main/resources/db/changelog/tenant/sql/45-unique-dossier-name.sql @@ -0,0 +1,15 @@ +-- update duplicate dossier names +update dossier d1 set dossier_name = ( + select + case when cnt = 1 then dossier_name + else dossier_name || ' ' || rn + end as dossier_name + from + ( + select *, row_number() over w rn, count(*) over w cnt + from dossier + window w as (partition by dossier_name) + ) t where t.id = d1.id); + +-- create unique index on dossier_name and hard_deleted_time +CREATE UNIQUE INDEX dossier_name_index ON dossier (dossier_name, (hard_deleted_time IS NULL)) WHERE hard_deleted_time IS NULL; diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java index 819a0342a..a6dde3b95 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/DossierTest.java @@ -6,7 +6,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.time.OffsetDateTime; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext; import org.junit.Test; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -50,6 +55,31 @@ public class DossierTest extends AbstractPersistenceServerServiceTest { @Autowired private WatermarkClient watermarkClient; + @Test + public void testDossierRaceCondition() { + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + IntStream.range(0, 30).parallel().forEach(x -> dossierTesterAndProvider.provideTestDossier(dossierTemplate, "dossier: " + x)); + var allDossiers = dossierClient.getAllDossiers(true, true); + var dossierCount = allDossiers.size(); + assertThat(dossierCount).isEqualTo(30); + + } + + + @Test + public void testCreateDuplicateDossier() { + var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate(); + IntStream.range(0, 30).parallel().forEach(x -> { + try { + dossierTesterAndProvider.provideTestDossier(dossierTemplate, "sameNameDossier"); + } catch (Exception e) { + // conflict exception is expected + } + }); + var allDossiers = dossierClient.getAllDossiers(true, true); + var dossierCount = allDossiers.size(); + assertThat(dossierCount).isEqualTo(1); + } @Test public void testDossier() {