From cbbe5a2801f1987a9bbb08c1c565daabe51b2398 Mon Sep 17 00:00:00 2001 From: Ruggero Corsaletti Date: Mon, 10 Jun 2024 17:54:53 +0200 Subject: [PATCH] AAE-22377 Exception if user exceed pagination limit (#1449) * [AAE-22377] Exception if user exceed pagination limit * [AAE-22377] code cleanup * [AAE-22377] test fix * [AAE-22377] code cleanup * Fixed comments on PR and handled 400 return code in the Exception Halndler * Fixed comments on PR and handled 400 return code in the Exception Halndler * code cleanup * Swagget Test fix * Changed default result count to 25 elements when maxItems is not specified * Reverted default result count to 100 elements when maxItems is not specified * Cleanup code and changed size parameter to size * Fixed comments in the PR and Test cleanup * Fixed comments in the PR and Test cleanup * Fixed comments in the PR and Test cleanup * Fixed comments in the PR and Test cleanup * Fixed comments in the PR and Test cleanup * Disabled enabled by default * Disabled enabled by default * Disabled enabled by default --- .../rest/CommonExceptionHandlerQuery.java | 11 + .../query/rest/TaskEntityControllerIT.java | 83 +- .../src/test/resources/swagger-expected.json | 810 +++++++++++++----- .../AlfrescoPageArgumentMethodResolver.java | 28 +- .../config/AlfrescoWebAutoConfiguration.java | 12 +- .../config/alfresco-rest-config.properties | 3 + ...lfrescoPageArgumentMethodResolverTest.java | 15 +- .../AlfrescoWebAutoConfigurationTest.java | 2 +- 8 files changed, 718 insertions(+), 246 deletions(-) diff --git a/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/main/java/org/activiti/cloud/services/query/rest/CommonExceptionHandlerQuery.java b/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/main/java/org/activiti/cloud/services/query/rest/CommonExceptionHandlerQuery.java index 849eebbad6b..c9eaafb2e26 100644 --- a/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/main/java/org/activiti/cloud/services/query/rest/CommonExceptionHandlerQuery.java +++ b/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/main/java/org/activiti/cloud/services/query/rest/CommonExceptionHandlerQuery.java @@ -21,6 +21,7 @@ import org.activiti.core.common.spring.security.policies.ActivitiForbiddenException; import org.springframework.hateoas.EntityModel; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -38,4 +39,14 @@ public EntityModel handleAppException( response.setContentType("application/json"); return EntityModel.of(new ActivitiErrorMessageImpl(HttpStatus.FORBIDDEN.value(), ex.getMessage())); } + + @ExceptionHandler(IllegalStateException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public EntityModel handleAppException( + IllegalStateException ex, + HttpServletResponse response + ) { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + return EntityModel.of(new ActivitiErrorMessageImpl(HttpStatus.BAD_REQUEST.value(), ex.getMessage())); + } } diff --git a/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/test/java/org/activiti/cloud/services/query/rest/TaskEntityControllerIT.java b/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/test/java/org/activiti/cloud/services/query/rest/TaskEntityControllerIT.java index 6152bb2a9e7..9a91cc0fa0c 100644 --- a/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/test/java/org/activiti/cloud/services/query/rest/TaskEntityControllerIT.java +++ b/activiti-cloud-query-service/activiti-cloud-services-query/activiti-cloud-services-query-rest/src/test/java/org/activiti/cloud/services/query/rest/TaskEntityControllerIT.java @@ -24,6 +24,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.querydsl.core.types.Predicate; @@ -65,13 +66,21 @@ @WebMvcTest(TaskController.class) @Import( - { QueryRestWebMvcAutoConfiguration.class, CommonModelAutoConfiguration.class, AlfrescoWebAutoConfiguration.class } + { + QueryRestWebMvcAutoConfiguration.class, + CommonModelAutoConfiguration.class, + AlfrescoWebAutoConfiguration.class, + CommonExceptionHandlerQuery.class, + } ) @EnableSpringDataWebSupport @AutoConfigureMockMvc @WithMockUser -@TestPropertySource("classpath:application-test.properties") -public class TaskEntityControllerIT { +@TestPropertySource( + locations = { "classpath:application-test.properties" }, + properties = "activiti.cloud.rest.max-items.enabled=true" +) +class TaskEntityControllerIT { @Autowired private MockMvc mockMvc; @@ -114,7 +123,7 @@ void setUp() { } @Test - public void findAllShouldReturnAllResultsUsingAlfrescoMetadataWhenMediaTypeIsApplicationJson() throws Exception { + void findAllShouldReturnAllResultsUsingAlfrescoMetadataWhenMediaTypeIsApplicationJson() throws Exception { //given AlfrescoPageRequest pageRequest = new AlfrescoPageRequest(11, 10, PageRequest.of(0, 20)); @@ -142,7 +151,7 @@ public void findAllShouldReturnAllResultsUsingAlfrescoMetadataWhenMediaTypeIsApp } @Test - public void findAllShouldReturnAllResultsUsingHalWhenMediaTypeIsApplicationHalJson() throws Exception { + void findAllShouldReturnAllResultsUsingHalWhenMediaTypeIsApplicationHalJson() throws Exception { //given PageRequest pageRequest = PageRequest.of(1, 10); @@ -157,7 +166,7 @@ public void findAllShouldReturnAllResultsUsingHalWhenMediaTypeIsApplicationHalJs } @Test - public void findByIdShouldUseAlfrescoMetadataWhenMediaTypeIsApplicationJson() throws Exception { + void findByIdShouldUseAlfrescoMetadataWhenMediaTypeIsApplicationJson() throws Exception { //given TaskEntity taskEntity = buildDefaultTask(); given(entityFinder.findById(eq(taskRepository), eq(taskEntity.getId()), anyString())).willReturn(taskEntity); @@ -174,7 +183,7 @@ public void findByIdShouldUseAlfrescoMetadataWhenMediaTypeIsApplicationJson() th } @Test - public void should_returnCandidates_when_invokeGetTaskById() throws Exception { + void should_returnCandidates_when_invokeGetTaskById() throws Exception { //given TaskEntity taskEntity = buildDefaultTask(); taskEntity.setTaskCandidateGroups(buildCandidateGroups(taskEntity)); @@ -227,7 +236,7 @@ private Set buildCandidateUsers(TaskEntity taskEntity) } @Test - public void should_returnTaskPermissions_when_invokeGetTaskById() throws Exception { + void should_returnTaskPermissions_when_invokeGetTaskById() throws Exception { //given TaskEntity taskEntity = buildDefaultTask(); taskEntity.setTaskCandidateUsers(buildCandidateUsers(taskEntity)); @@ -250,4 +259,62 @@ public void should_returnTaskPermissions_when_invokeGetTaskById() throws Excepti .ofLength(1) .thatContains(TaskPermissions.VIEW); } + + @Test + void should_returnBadRequest_when_invokeWithPagingParametersExceedingLimits() throws Exception { + //given + AlfrescoPageRequest pageRequest = new AlfrescoPageRequest(1000, 1000, PageRequest.of(0, 1000)); + + given(taskRepository.findAll(any(), eq(pageRequest))) + .willReturn(new PageImpl<>(Collections.singletonList(buildDefaultTask()), pageRequest, 2000)); + + //when + mockMvc + .perform(get("/v1/tasks?skipCount=1000&maxItems=1001").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.entry.message").value("Exceeded max limit of 1000 elements")); + } + + @Test + void should_returnBadRequest_when_invokeWithPageParameterExceedingLimits() throws Exception { + //given + AlfrescoPageRequest pageRequest = new AlfrescoPageRequest(1000, 1000, PageRequest.of(0, 1000)); + + given(taskRepository.findAll(any(), eq(pageRequest))) + .willReturn(new PageImpl<>(Collections.singletonList(buildDefaultTask()), pageRequest, 2000)); + + //when + mockMvc + .perform(get("/v1/tasks?page=0&size=1001").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.entry.message").value("Exceeded max limit of 1000 elements")); + } + + @Test + void should_returnOK_when_invokeWithPagingParametersWithinLimits() throws Exception { + //given + AlfrescoPageRequest pageRequest = new AlfrescoPageRequest(0, 1000, PageRequest.of(0, 20)); + + given(taskRepository.findAll(any(), eq(pageRequest))) + .willReturn(new PageImpl<>(Collections.singletonList(buildDefaultTask()), pageRequest, 1001)); + + //when + mockMvc + .perform(get("/v1/tasks?skipCount=0&maxItems=1000").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void should_returnOK_when_invokeWithPageParameterWithinLimits() throws Exception { + //given + PageRequest pageRequest = PageRequest.of(0, 1000); + + given(taskRepository.findAll(any(), eq(pageRequest))) + .willReturn(new PageImpl<>(Collections.singletonList(buildDefaultTask()), pageRequest, 1001)); + + //when + mockMvc + .perform(get("/v1/tasks?page=0&size=1000").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } } diff --git a/activiti-cloud-query-service/activiti-cloud-starter-query/src/test/resources/swagger-expected.json b/activiti-cloud-query-service/activiti-cloud-starter-query/src/test/resources/swagger-expected.json index ac0ade52c47..2c11819e3a7 100644 --- a/activiti-cloud-query-service/activiti-cloud-starter-query/src/test/resources/swagger-expected.json +++ b/activiti-cloud-query-service/activiti-cloud-starter-query/src/test/resources/swagger-expected.json @@ -389,6 +389,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -770,6 +780,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -1095,6 +1115,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -1396,6 +1426,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -1690,6 +1730,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -1946,6 +1996,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2354,6 +2414,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2455,6 +2525,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2512,6 +2592,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2569,6 +2659,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2632,6 +2732,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2939,6 +3049,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -2996,6 +3116,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3104,6 +3234,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3204,6 +3344,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3502,6 +3652,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3559,6 +3719,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3696,6 +3866,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3753,6 +3933,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3847,6 +4037,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -3948,6 +4148,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4005,6 +4215,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4062,6 +4282,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4125,6 +4355,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4209,6 +4449,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4266,6 +4516,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4323,6 +4583,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4380,6 +4650,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4488,6 +4768,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4574,6 +4864,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4660,6 +4960,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4717,6 +5027,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4854,6 +5174,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -4911,6 +5241,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -5005,6 +5345,16 @@ } } }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntryResponseContentActivitiErrorMessage" + } + } + } + }, "200": { "description": "OK", "content": { @@ -5109,19 +5459,19 @@ "title": "CloudVariableInstance_ProcessVariables", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5136,14 +5486,14 @@ "type": { "type": "string" }, + "processInstanceId": { + "type": "string" + }, "taskVariable": { "type": "boolean" }, "taskId": { "type": "string" - }, - "processInstanceId": { - "type": "string" } } }, @@ -5231,19 +5581,19 @@ "$ref": "#/components/schemas/CloudVariableInstance_ProcessVariables" } }, - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5273,44 +5623,6 @@ "type": "integer", "format": "int32" }, - "status": { - "type": "string", - "enum": [ - "CREATED", - "ASSIGNED", - "SUSPENDED", - "COMPLETED", - "CANCELLED", - "DELETED" - ] - }, - "description": { - "type": "string" - }, - "businessKey": { - "type": "string" - }, - "completedBy": { - "type": "string" - }, - "createdDate": { - "type": "string", - "format": "date-time" - }, - "dueDate": { - "type": "string", - "format": "date-time" - }, - "assignee": { - "type": "string" - }, - "claimedDate": { - "type": "string", - "format": "date-time" - }, - "formKey": { - "type": "string" - }, "candidateUsers": { "type": "array", "items": { @@ -5323,6 +5635,12 @@ "type": "string" } }, + "processInstanceId": { + "type": "string" + }, + "processDefinitionId": { + "type": "string" + }, "completedDate": { "type": "string", "format": "date-time" @@ -5333,10 +5651,42 @@ "taskDefinitionKey": { "type": "string" }, - "processInstanceId": { + "businessKey": { "type": "string" }, - "processDefinitionId": { + "formKey": { + "type": "string" + }, + "assignee": { + "type": "string" + }, + "claimedDate": { + "type": "string", + "format": "date-time" + }, + "completedBy": { + "type": "string" + }, + "createdDate": { + "type": "string", + "format": "date-time" + }, + "dueDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "CREATED", + "ASSIGNED", + "SUSPENDED", + "COMPLETED", + "CANCELLED", + "DELETED" + ] + }, + "description": { "type": "string" } } @@ -5345,19 +5695,19 @@ "title": "CloudVariableInstance", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5372,14 +5722,14 @@ "type": { "type": "string" }, + "processInstanceId": { + "type": "string" + }, "taskVariable": { "type": "boolean" }, "taskId": { "type": "string" - }, - "processInstanceId": { - "type": "string" } } }, @@ -5445,19 +5795,19 @@ "title": "CloudVariableInstance_General", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5472,14 +5822,14 @@ "type": { "type": "string" }, + "processInstanceId": { + "type": "string" + }, "taskVariable": { "type": "boolean" }, "taskId": { "type": "string" - }, - "processInstanceId": { - "type": "string" } } }, @@ -5518,19 +5868,19 @@ "$ref": "#/components/schemas/CloudVariableInstance_General" } }, - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5560,44 +5910,6 @@ "type": "integer", "format": "int32" }, - "status": { - "type": "string", - "enum": [ - "CREATED", - "ASSIGNED", - "SUSPENDED", - "COMPLETED", - "CANCELLED", - "DELETED" - ] - }, - "description": { - "type": "string" - }, - "businessKey": { - "type": "string" - }, - "completedBy": { - "type": "string" - }, - "createdDate": { - "type": "string", - "format": "date-time" - }, - "dueDate": { - "type": "string", - "format": "date-time" - }, - "assignee": { - "type": "string" - }, - "claimedDate": { - "type": "string", - "format": "date-time" - }, - "formKey": { - "type": "string" - }, "candidateUsers": { "type": "array", "items": { @@ -5610,6 +5922,12 @@ "type": "string" } }, + "processInstanceId": { + "type": "string" + }, + "processDefinitionId": { + "type": "string" + }, "completedDate": { "type": "string", "format": "date-time" @@ -5620,10 +5938,42 @@ "taskDefinitionKey": { "type": "string" }, - "processInstanceId": { + "businessKey": { "type": "string" }, - "processDefinitionId": { + "formKey": { + "type": "string" + }, + "assignee": { + "type": "string" + }, + "claimedDate": { + "type": "string", + "format": "date-time" + }, + "completedBy": { + "type": "string" + }, + "createdDate": { + "type": "string", + "format": "date-time" + }, + "dueDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "CREATED", + "ASSIGNED", + "SUSPENDED", + "COMPLETED", + "CANCELLED", + "DELETED" + ] + }, + "description": { "type": "string" } } @@ -5632,19 +5982,19 @@ "title": "CloudProcessInstance_ProcessVariables", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5666,35 +6016,35 @@ "processDefinitionName": { "type": "string" }, - "status": { + "processDefinitionId": { + "type": "string" + }, + "completedDate": { "type": "string", - "enum": [ - "CREATED", - "RUNNING", - "SUSPENDED", - "CANCELLED", - "COMPLETED" - ] + "format": "date-time" }, - "parentId": { + "businessKey": { "type": "string" }, "initiator": { "type": "string" }, - "businessKey": { - "type": "string" - }, "startDate": { "type": "string", "format": "date-time" }, - "completedDate": { - "type": "string", - "format": "date-time" - }, - "processDefinitionId": { + "parentId": { "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "CREATED", + "RUNNING", + "SUSPENDED", + "CANCELLED", + "COMPLETED" + ] } } }, @@ -5735,19 +6085,19 @@ "title": "CloudProcessInstance_General", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5769,35 +6119,35 @@ "processDefinitionName": { "type": "string" }, - "status": { + "processDefinitionId": { + "type": "string" + }, + "completedDate": { "type": "string", - "enum": [ - "CREATED", - "RUNNING", - "SUSPENDED", - "CANCELLED", - "COMPLETED" - ] + "format": "date-time" }, - "parentId": { + "businessKey": { "type": "string" }, "initiator": { "type": "string" }, - "businessKey": { - "type": "string" - }, "startDate": { "type": "string", "format": "date-time" }, - "completedDate": { - "type": "string", - "format": "date-time" - }, - "processDefinitionId": { + "parentId": { "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "CREATED", + "RUNNING", + "SUSPENDED", + "CANCELLED", + "COMPLETED" + ] } } }, @@ -5863,19 +6213,19 @@ "title": "CloudProcessDefinition", "type": "object", "properties": { - "serviceName": { + "serviceFullName": { "type": "string" }, - "appName": { + "serviceVersion": { "type": "string" }, - "serviceType": { + "serviceName": { "type": "string" }, - "serviceFullName": { + "appName": { "type": "string" }, - "serviceVersion": { + "serviceType": { "type": "string" }, "appVersion": { @@ -5894,14 +6244,14 @@ "type": "integer", "format": "int32" }, - "description": { - "type": "string" - }, "formKey": { "type": "string" }, "category": { "type": "string" + }, + "description": { + "type": "string" } } }, @@ -6000,6 +6350,21 @@ "type": "integer", "format": "int32" }, + "completedDate": { + "type": "string", + "format": "date-time" + }, + "cancelledDate": { + "type": "string", + "format": "date-time" + }, + "startedDate": { + "type": "string", + "format": "date-time" + }, + "businessKey": { + "type": "string" + }, "status": { "type": "string", "enum": [ @@ -6009,20 +6374,11 @@ "ERROR" ] }, - "businessKey": { + "serviceFullName": { "type": "string" }, - "startedDate": { - "type": "string", - "format": "date-time" - }, - "completedDate": { - "type": "string", - "format": "date-time" - }, - "cancelledDate": { - "type": "string", - "format": "date-time" + "serviceVersion": { + "type": "string" }, "serviceName": { "type": "string" @@ -6033,25 +6389,16 @@ "serviceType": { "type": "string" }, - "serviceFullName": { - "type": "string" - }, - "serviceVersion": { - "type": "string" - }, "appVersion": { "type": "string" }, - "executionId": { - "type": "string" - }, "activityType": { "type": "string" }, "activityName": { "type": "string" }, - "elementId": { + "executionId": { "type": "string" }, "processInstanceId": { @@ -6059,6 +6406,9 @@ }, "processDefinitionId": { "type": "string" + }, + "elementId": { + "type": "string" } } }, @@ -6102,29 +6452,6 @@ "errorMessage": { "type": "string" }, - "status": { - "type": "string", - "enum": [ - "INTEGRATION_REQUESTED", - "INTEGRATION_RESULT_RECEIVED", - "INTEGRATION_ERROR_RECEIVED" - ] - }, - "errorCode": { - "type": "string" - }, - "requestDate": { - "type": "string", - "format": "date-time" - }, - "resultDate": { - "type": "string", - "format": "date-time" - }, - "errorDate": { - "type": "string", - "format": "date-time" - }, "stackTraceElements": { "type": "array", "items": { @@ -6161,6 +6488,29 @@ "errorClassName": { "type": "string" }, + "requestDate": { + "type": "string", + "format": "date-time" + }, + "resultDate": { + "type": "string", + "format": "date-time" + }, + "errorDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": [ + "INTEGRATION_REQUESTED", + "INTEGRATION_RESULT_RECEIVED", + "INTEGRATION_ERROR_RECEIVED" + ] + }, + "errorCode": { + "type": "string" + }, "id": { "type": "string" }, @@ -6177,22 +6527,10 @@ "parentProcessInstanceId": { "type": "string" }, - "clientId": { - "type": "string" - }, - "clientName": { - "type": "string" - }, - "businessKey": { - "type": "string" - }, - "executionId": { - "type": "string" - }, - "clientType": { + "processInstanceId": { "type": "string" }, - "appVersion": { + "processDefinitionId": { "type": "string" }, "outBoundVariables": { @@ -6210,19 +6548,22 @@ "type": "object" } }, - "processInstanceId": { + "appVersion": { "type": "string" }, - "processDefinitionId": { + "executionId": { "type": "string" }, - "serviceName": { + "businessKey": { "type": "string" }, - "appName": { + "clientType": { "type": "string" }, - "serviceType": { + "clientId": { + "type": "string" + }, + "clientName": { "type": "string" }, "serviceFullName": { @@ -6230,6 +6571,15 @@ }, "serviceVersion": { "type": "string" + }, + "serviceName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "serviceType": { + "type": "string" } } }, diff --git a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolver.java b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolver.java index ecd2e18af73..15ff3b1798a 100644 --- a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolver.java +++ b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolver.java @@ -29,12 +29,19 @@ public class AlfrescoPageArgumentMethodResolver implements PageableArgumentResol private final AlfrescoPageParameterParser pageParameterParser; private final PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver; + private final int maxItemsLimit; + private final boolean maxItemsLimitEnabled; + public AlfrescoPageArgumentMethodResolver( AlfrescoPageParameterParser pageParameterParser, - PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver + PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver, + int maxItemsLimit, + boolean maxItemsLimitEnabled ) { this.pageParameterParser = pageParameterParser; this.pageableHandlerMethodArgumentResolver = pageableHandlerMethodArgumentResolver; + this.maxItemsLimit = maxItemsLimit; + this.maxItemsLimitEnabled = maxItemsLimitEnabled; } @Override @@ -58,7 +65,10 @@ public Pageable resolveArgument( ); AlfrescoQueryParameters alfrescoQueryParameters = pageParameterParser.parseParameters(webRequest); - if ( + + if (isPaginationValueExceedingLimit(alfrescoQueryParameters, basePageable)) { + throw new IllegalStateException("Exceeded max limit of " + maxItemsLimit + " elements"); + } else if ( alfrescoQueryParameters.getSkipCountParameter().isSet() || alfrescoQueryParameters.getMaxItemsParameter().isSet() ) { @@ -71,4 +81,18 @@ public Pageable resolveArgument( return basePageable; } } + + private boolean isPaginationValueExceedingLimit( + AlfrescoQueryParameters alfrescoQueryParameters, + Pageable basePageable + ) { + if (maxItemsLimitEnabled) { + if (alfrescoQueryParameters.getMaxItemsParameter().isSet()) { + return alfrescoQueryParameters.getMaxItemsParameter().getValue() > maxItemsLimit; + } else { + return basePageable.getPageSize() > maxItemsLimit; + } + } + return false; + } } diff --git a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfiguration.java b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfiguration.java index 1d9cf1d7652..30fe19aa08b 100644 --- a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfiguration.java +++ b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfiguration.java @@ -50,13 +50,19 @@ public class AlfrescoWebAutoConfiguration implements WebMvcConfigurer { private final PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver; private final int defaultPageSize; + private final int maxItemsLimit; + private final boolean maxItemsLimitEnabled; public AlfrescoWebAutoConfiguration( @Lazy PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver, - @Value("${spring.data.rest.default-page-size:100}") int defaultPageSize + @Value("${spring.data.rest.default-page-size:100}") int defaultPageSize, + @Value("${activiti.cloud.rest.max-items}") int maxItemsLimit, + @Value("${activiti.cloud.rest.max-items.enabled}") boolean maxItemsLimitEnabled ) { this.pageableHandlerMethodArgumentResolver = pageableHandlerMethodArgumentResolver; this.defaultPageSize = defaultPageSize; + this.maxItemsLimit = maxItemsLimit; + this.maxItemsLimitEnabled = maxItemsLimitEnabled; } @Override @@ -65,7 +71,9 @@ public void addArgumentResolvers(List resolvers) 0, new AlfrescoPageArgumentMethodResolver( new AlfrescoPageParameterParser(defaultPageSize), - pageableHandlerMethodArgumentResolver + pageableHandlerMethodArgumentResolver, + maxItemsLimit, + maxItemsLimitEnabled ) ); } diff --git a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/resources/config/alfresco-rest-config.properties b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/resources/config/alfresco-rest-config.properties index bcb91970054..18083da80a3 100644 --- a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/resources/config/alfresco-rest-config.properties +++ b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/main/resources/config/alfresco-rest-config.properties @@ -1,3 +1,6 @@ spring.data.rest.default-page-size=100 spring.data.rest.default-media-type=application/json spring.hateoas.use-hal-as-default-json-media-type=false + +activiti.cloud.rest.max-items=${MAX_ITEMS_LIMIT:1000} +activiti.cloud.rest.max-items.enabled=${MAX_ITEMS_LIMIT_ENABLED:true} diff --git a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolverTest.java b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolverTest.java index 40c4aa933ec..2080225f58d 100644 --- a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolverTest.java +++ b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/argument/resolver/AlfrescoPageArgumentMethodResolverTest.java @@ -20,11 +20,10 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import java.util.Collections; import org.activiti.test.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.MethodParameter; @@ -37,7 +36,6 @@ @ExtendWith(MockitoExtension.class) public class AlfrescoPageArgumentMethodResolverTest { - @InjectMocks private AlfrescoPageArgumentMethodResolver alfrescoPageArgumentMethodResolver; @Mock @@ -46,6 +44,17 @@ public class AlfrescoPageArgumentMethodResolverTest { @Mock private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver; + @BeforeEach + public void setUp() { + this.alfrescoPageArgumentMethodResolver = + new AlfrescoPageArgumentMethodResolver( + pageParameterParser, + pageableHandlerMethodArgumentResolver, + 1000, + true + ); + } + @Test public void supportsParameterShouldReturnTrueWhenItsAPageable() throws Exception { //given diff --git a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfigurationTest.java b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfigurationTest.java index 345686cb7e3..da5e5657223 100644 --- a/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfigurationTest.java +++ b/activiti-cloud-service-common/activiti-cloud-services-dbp-rest/src/test/java/org/activiti/cloud/alfresco/config/AlfrescoWebAutoConfigurationTest.java @@ -45,7 +45,7 @@ public class AlfrescoWebAutoConfigurationTest { @BeforeEach public void setUp() { - configurer = new AlfrescoWebAutoConfiguration(pageableHandlerMethodArgumentResolver, 100); + configurer = new AlfrescoWebAutoConfiguration(pageableHandlerMethodArgumentResolver, 100, 1000, true); } @Test