Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ ext {
set('spring-framework.version', '6.2.14')
set('spring-security.version', '6.5.7')
set('log4j2.version', '2.24.3')
set('jackson.version', '2.16.0')
set('snakeyaml.version', '2.2')
junit = '5.14.3'
junitPlatform = '1.14.3'
Expand Down
94 changes: 0 additions & 94 deletions src/main/java/uk/gov/hmcts/ccd/config/HalConfig.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -196,8 +198,27 @@ public ResponseEntity<CaseAssignedUserRolesResponse> removeCaseUserRoles(
* `414 URI Too Long` issues, see <a href="https://tools.hmcts.net/jira/browse/CCD-3588">CCD-3588</a>.
*/
@Deprecated(forRemoval = true)
/*
NOTE: Explicitly set to application/hal+json to bypass a Spring Framework concurrency problem
in AbstractJackson2HttpMessageConverter (Issue #36090).
* Although the response bodies may suppress _links (via @JsonIgnore), we strictly enforce
the HAL media type here for two critical reasons:
* 1. CRASH PREVENTION: It forces Spring to select the specific HAL converter (fast path)
instead of iterating over all converters to discover supported types. The iteration
path triggers an ArrayIndexOutOfBoundsException on a corrupted LinkedHashMap
during concurrent startup (Lazy Initialization race condition).
* 2. COMPATIBILITY: It preserves the existing Content-Type header (application/hal+json)
that clients expect, preventing contract breakage.
* NOTE: GET /case-users additionally produces application/json to maintain backwards
compatibility with consuming services that send Accept: application/json. This is safe
as the response body is identical in both cases — HAL-specific _links are suppressed
via @JsonIgnore on CaseAssignedUserRolesResource.
* WARNING: DO NOT change this to MediaType.APPLICATION_JSON_VALUE or remove it
without verifying that the upstream apps fix has been applied.
*/
@GetMapping(
path = "/case-users"
path = "/case-users",
produces = {MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
@Operation(
summary = "Get Case-Assigned Users and Roles",
Expand Down
148 changes: 0 additions & 148 deletions src/test/java/uk/gov/hmcts/ccd/config/HalConfigTest.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package uk.gov.hmcts.ccd.v2.external.controller;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import uk.gov.hmcts.ccd.ApplicationParams;
import uk.gov.hmcts.ccd.data.SecurityUtils;
import uk.gov.hmcts.ccd.domain.model.std.CaseAssignedUserRole;
import uk.gov.hmcts.ccd.domain.service.cauroles.CaseAssignedUserRolesOperation;
import uk.gov.hmcts.ccd.domain.service.common.UIDService;

import java.util.List;

import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class CaseAssignedUserRolesControllerContentNegotiationTest {

private static final String CASE_ID_GOOD = "4444333322221111";

@Mock
private ApplicationParams applicationParams;

@Mock
private UIDService caseReferenceService;

@Mock
private CaseAssignedUserRolesOperation caseAssignedUserRolesOperation;

@Mock
private SecurityUtils securityUtils;

private MockMvc mockMvc;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);

CaseAssignedUserRolesController controller = new CaseAssignedUserRolesController(
applicationParams,
caseReferenceService,
caseAssignedUserRolesOperation,
securityUtils
);

mockMvc = MockMvcBuilders
.standaloneSetup(controller)
.build();

when(caseReferenceService.validateUID(anyString())).thenReturn(true);
when(caseAssignedUserRolesOperation.findCaseUserRoles(anyList(), anyList()))
.thenReturn(List.of(new CaseAssignedUserRole()));
}

@Nested
@DisplayName("GET /case-users content negotiation")
class GetCaseUserRolesContentNegotiation {

@Test
@DisplayName("should return 200 when Accept header is application/json")
void getCaseUserRoles_shouldReturn200_whenAcceptHeaderIsApplicationJson() throws Exception {
mockMvc.perform(get("/case-users")
.param("case_ids", CASE_ID_GOOD)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
@DisplayName("should return 200 when Accept header is application/hal+json")
void getCaseUserRoles_shouldReturn200_whenAcceptHeaderIsHalJson() throws Exception {
mockMvc.perform(get("/case-users")
.param("case_ids", CASE_ID_GOOD)
.accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk());
}

@Test
@DisplayName("should return 200 when Accept header is wildcard")
void getCaseUserRoles_shouldReturn200_whenAcceptHeaderIsWildcard() throws Exception {
mockMvc.perform(get("/case-users")
.param("case_ids", CASE_ID_GOOD)
.accept(MediaType.ALL))
.andExpect(status().isOk());
}

@Test
@DisplayName("should return 406 when Accept header is unsupported media type")
void getCaseUserRoles_shouldReturn406_whenAcceptHeaderIsUnsupported() throws Exception {
mockMvc.perform(get("/case-users")
.param("case_ids", CASE_ID_GOOD)
.accept(MediaType.APPLICATION_XML))
.andExpect(status().isNotAcceptable());
}
}
}