From d3abf51a0bd7b770569b624c2da4f2008ef27ec5 Mon Sep 17 00:00:00 2001 From: Dave Thompson Date: Wed, 5 Feb 2025 15:52:06 +0000 Subject: [PATCH] DMP-4653 Fixing courtroom name unique constraint violation (#2536) --- .../NodeRegistrationControllerTest.java | 13 ++- .../service/RetrieveCoreObjectService.java | 94 ++++++++++++++++--- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/integrationTest/java/uk/gov/hmcts/darts/noderegistration/controller/NodeRegistrationControllerTest.java b/src/integrationTest/java/uk/gov/hmcts/darts/noderegistration/controller/NodeRegistrationControllerTest.java index c5fd725086..f65e1a077a 100644 --- a/src/integrationTest/java/uk/gov/hmcts/darts/noderegistration/controller/NodeRegistrationControllerTest.java +++ b/src/integrationTest/java/uk/gov/hmcts/darts/noderegistration/controller/NodeRegistrationControllerTest.java @@ -1,7 +1,9 @@ package uk.gov.hmcts.darts.noderegistration.controller; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -10,7 +12,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.transaction.annotation.Transactional; import uk.gov.hmcts.darts.authorisation.component.UserIdentity; import uk.gov.hmcts.darts.common.entity.CourthouseEntity; import uk.gov.hmcts.darts.common.entity.CourtroomEntity; @@ -34,7 +35,6 @@ @Slf4j @AutoConfigureMockMvc -@Transactional class NodeRegistrationControllerTest extends IntegrationBase { @Autowired @@ -43,6 +43,15 @@ class NodeRegistrationControllerTest extends IntegrationBase { @MockitoBean private UserIdentity mockUserIdentity; + @BeforeEach + void startHibernateSession() { + openInViewUtil.openEntityManager(); + } + + @AfterEach + void closeHibernateSession() { + openInViewUtil.closeEntityManager(); + } @Test void testPostRegisterDevices() throws Exception { diff --git a/src/main/java/uk/gov/hmcts/darts/common/service/RetrieveCoreObjectService.java b/src/main/java/uk/gov/hmcts/darts/common/service/RetrieveCoreObjectService.java index 3158444535..dd624abb87 100644 --- a/src/main/java/uk/gov/hmcts/darts/common/service/RetrieveCoreObjectService.java +++ b/src/main/java/uk/gov/hmcts/darts/common/service/RetrieveCoreObjectService.java @@ -4,6 +4,8 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import uk.gov.hmcts.darts.common.entity.CourtCaseEntity; import uk.gov.hmcts.darts.common.entity.CourthouseEntity; import uk.gov.hmcts.darts.common.entity.CourtroomEntity; @@ -16,39 +18,109 @@ public interface RetrieveCoreObjectService { - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used when events are received and processed. + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests to create the + * same hearing can be processed without causing a constraint violation. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) HearingEntity retrieveOrCreateHearing(String courthouseName, String courtroomName, String caseNumber, LocalDateTime hearingDate); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used for the following + * - daily list processing + * - adding audio using metadata + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests to create the + * same hearing can be processed without causing a constraint violation. + * Concurrency isn't a concern for daily list processing. + */ + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) HearingEntity retrieveOrCreateHearing(String courthouseName, String courtroomName, String caseNumber, LocalDateTime hearingDate, UserAccountEntity userAccount); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * Retrieve or create a case and link to media. + * @deprecated This method is only used by tests. Tests should be refactored and this method should be removed. + */ + @SuppressWarnings("java:S1133") // suppress sonar warning about deprecated methods + @Deprecated(since = "04/02/2025") HearingEntity retrieveOrCreateHearingWithMedia(String courthouseName, String courtroomName, String caseNumber, LocalDateTime hearingDate, UserAccountEntity userAccount, MediaEntity mediaEntity); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * Retrieve or create a courtroom. + * @deprecated This method is only used by tests. + * Tests should be refactored to use the other `retrieveOrCreateCourtroom` method, and this method should be removed. + */ + @SuppressWarnings("java:S1133") // suppress sonar warning about deprecated methods + @Deprecated(since = "04/02/2025") CourtroomEntity retrieveOrCreateCourtroom(CourthouseEntity courthouse, String courtroomName, UserAccountEntity userAccount); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used for the following + * - node register + * - events + * - get cases + * - adding audio + * - audio requests + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests that attempt to create the + * same courtroom can be processed without causing a constraint violation. + * Concurrency isn't a concern for adding audio or audio requests. Due to the nature of these calls, it is extremely unlikely that the courtroom + * will not exist. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) CourtroomEntity retrieveOrCreateCourtroom(String courthouseName, String courtroomName, UserAccountEntity userAccount); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used for the following + * - add case + * - events + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests that attempt to create the + * same case can be processed without causing a constraint violation. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) CourtCaseEntity retrieveOrCreateCase(String courthouseName, String caseNumber); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * Retrieve or create a case. + * @deprecated This method is only used by tests. + * Tests should be refactored to use the other `retrieveOrCreateCase` method, and this method should be removed. + */ + @SuppressWarnings("java:S1133") // suppress sonar warning about deprecated methods + @Deprecated(since = "04/02/2025") CourtCaseEntity retrieveOrCreateCase(String courthouseName, String caseNumber, UserAccountEntity userAccount); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used during add audio when retrieving/creating cases used in the metadata before adding to the media_linked_case table. + * It's possible that the same case could be sent in concurrent add audio requests. + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests that attempt to create the + * same case can be processed without causing a constraint violation. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) CourtCaseEntity retrieveOrCreateCase(CourthouseEntity courthouse, String caseNumber, UserAccountEntity userAccount); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * Used to retrieve the courthouse during add audio validation. + */ CourthouseEntity retrieveCourthouse(String courthouseName); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is used for add case, it's possible that the same judge could be sent in concurrent add case requests. + * By creating a new transaction, along with using the @Retryable annotation, we can ensure that concurrent requests that attempt to create the + * same judge can be processed without causing a constraint violation. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}, maxAttempts = 10) JudgeEntity retrieveOrCreateJudge(String judgeName); - @Retryable(backoff = @Backoff(delay = 50), retryFor = {DataIntegrityViolationException.class, PSQLException.class}) + /** + * This method is only used to add judges during daily list processing. This task processed data in serial and therefore + * there are no concurrent write concerns. + */ JudgeEntity retrieveOrCreateJudge(String judgeName, UserAccountEntity userAccount); } \ No newline at end of file