From: aravind.est Date: Thu, 10 Jul 2025 17:37:03 +0000 (+0100) Subject: Request validation added X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=780f9104bf05614dc3932f4ac441f96d023c0038;p=nonrtric%2Fplt%2Frappmanager.git Request validation added Request parameters and payload validation added. Issue-ID: NONRTRIC-1080 Change-Id: I646eee69d18fb74f2806610d1e123a13dbc2cd0e Signed-off-by: aravind.est --- diff --git a/pom.xml b/pom.xml index d9922d9..2e2ab70 100755 --- a/pom.xml +++ b/pom.xml @@ -56,8 +56,6 @@ 0.2.6 4.0.1 6.2.8 - 3.1.1 - 3.0.0 6.1.0 2.2.34 2.1.0-alpha1 diff --git a/rapp-manager-application/pom.xml b/rapp-manager-application/pom.xml index 09ee2b5..cc571f9 100755 --- a/rapp-manager-application/pom.xml +++ b/rapp-manager-application/pom.xml @@ -98,6 +98,10 @@ org.springframework.boot spring-boot-starter-cache + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-configuration-processor @@ -129,17 +133,6 @@ jackson-databind-nullable ${openapi.jackson.databind.nullable.version} - - - jakarta.validation - jakarta.validation-api - ${jakarta.validation.api.version} - - - jakarta.annotation - jakarta.annotation-api - ${jakarta.annotation.api.version} - io.swagger.core.v3 swagger-annotations diff --git a/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/ExceptionControllerHandler.java b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/ExceptionControllerHandler.java new file mode 100644 index 0000000..7f76d1a --- /dev/null +++ b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/ExceptionControllerHandler.java @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START====================================================================== + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.rest; + +import java.util.Objects; +import java.util.stream.Collectors; +import org.oransc.rappmanager.models.exception.ErrorResponse; +import org.oransc.rappmanager.models.exception.RappHandlerException; +import org.oransc.rappmanager.models.exception.RappValidationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.support.MissingServletRequestPartException; + +@RestControllerAdvice +public class ExceptionControllerHandler { + + @ExceptionHandler(RappHandlerException.class) + public ResponseEntity handleRappHandlerException(RappHandlerException rappHandlerException) { + return ResponseEntity.status(rappHandlerException.getStatusCode()) + .body(new ErrorResponse(rappHandlerException.getMessage())); + } + + @ExceptionHandler(RappValidationException.class) + public ResponseEntity handleRappValidationException( + RappValidationException rappValidationException) { + return ResponseEntity.status(rappValidationException.getStatusCode()) + .body(new ErrorResponse(rappValidationException.getMessage())); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("Request body is not in the expected format.")); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + String errorMessage = "Validation failed for the request body: [ %s ]"; + if (ex.getBindingResult().getFieldErrors().isEmpty()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse( + "Validation failed for the request body. " + Objects.requireNonNull(ex.getGlobalError()) + .getDefaultMessage())); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse(String.format(errorMessage, + ex.getBindingResult().getFieldErrors().stream() + .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) + .collect(Collectors.joining(", "))))); + } + + @ExceptionHandler({MultipartException.class, MissingServletRequestPartException.class}) + public ResponseEntity handleMultipartException(Exception ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse("Request multipart is not valid or not in the expected format.")); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException ex) { + String errorMessage = "Parameter '%s' is not in the expected format."; + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse(String.format(errorMessage, ex.getName()))); + } +} diff --git a/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappController.java b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappController.java index a5e698d..a8335f6 100644 --- a/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappController.java +++ b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappController.java @@ -19,6 +19,7 @@ package org.oransc.rappmanager.rest; +import jakarta.validation.Valid; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -77,7 +78,7 @@ public class RappController { @PostMapping("{rapp_id}") public ResponseEntity createRapp(@PathVariable("rapp_id") String rappId, - @RequestPart("file") MultipartFile csarFilePart) throws IOException { + @Valid @RequestPart("file") MultipartFile csarFilePart) throws IOException { if (rappValidationHandler.isValidRappPackage(csarFilePart)) { File csarFile = new File( rappCsarConfigurationHandler.getRappPackageLocation(rappManagerConfiguration.getCsarLocation(), @@ -103,7 +104,7 @@ public class RappController { @PutMapping("{rapp_id}") public ResponseEntity primeRapp(@PathVariable("rapp_id") String rappId, - @RequestBody RappPrimeOrder rappPrimeOrder) { + @Valid @RequestBody RappPrimeOrder rappPrimeOrder) { // @formatter:off return rappCacheService.getRapp(rappId) .map(rapp -> Optional.ofNullable(rappPrimeOrder.getPrimeOrder()) diff --git a/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappInstanceController.java b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappInstanceController.java index ee15d82..4d2d33a 100644 --- a/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappInstanceController.java +++ b/rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappInstanceController.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.oransc.rappmanager.rest; +import jakarta.validation.Valid; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -61,7 +62,7 @@ public class RappInstanceController { @PostMapping public ResponseEntity createRappInstance(@PathVariable("rapp_id") String rappId, - @RequestBody RappInstance rappInstance) { + @Valid @RequestBody RappInstance rappInstance) { return rappCacheService.getRapp(rappId).map(rapp -> { rappInstanceStateMachine.onboardRappInstance(rappInstance.getRappInstanceId()); rapp.getRappInstances().put(rappInstance.getRappInstanceId(), rappInstance); @@ -71,7 +72,7 @@ public class RappInstanceController { @GetMapping("{rapp_instance_id}") public ResponseEntity getRappInstance(@PathVariable("rapp_id") String rappId, - @PathVariable("rapp_instance_id") UUID rappInstanceId) { + @Valid @PathVariable("rapp_instance_id") UUID rappInstanceId) { return rappCacheService.getRapp(rappId).map(rapp -> Pair.of(rapp, rapp.getRappInstances().get(rappInstanceId))) .filter(rappPair -> rappPair.getLeft().getRappInstances().containsKey(rappInstanceId)) .map(rappPair -> { @@ -85,8 +86,8 @@ public class RappInstanceController { @PutMapping("{rapp_instance_id}") public ResponseEntity deployRappInstance(@PathVariable("rapp_id") String rappId, - @PathVariable("rapp_instance_id") UUID rappInstanceId, - @RequestBody RappInstanceDeployOrder rappInstanceDeployOrder) { + @Valid @PathVariable("rapp_instance_id") UUID rappInstanceId, + @Valid @RequestBody RappInstanceDeployOrder rappInstanceDeployOrder) { //@formatter:off return rappCacheService.getRapp(rappId) .filter(rapp -> rapp.getRappInstances().containsKey(rappInstanceId)) @@ -102,7 +103,7 @@ public class RappInstanceController { @DeleteMapping("{rapp_instance_id}") public ResponseEntity deleteRappInstance(@PathVariable("rapp_id") String rappId, - @PathVariable("rapp_instance_id") UUID rappInstanceId) { + @Valid @PathVariable("rapp_instance_id") UUID rappInstanceId) { return rappCacheService.getRapp(rappId).filter(rApp -> rApp.getRappInstances().containsKey(rappInstanceId)) .map(rApp -> rappService.deleteRappInstance(rApp, rappInstanceId)).orElseThrow( () -> new RappHandlerException(HttpStatus.NOT_FOUND, diff --git a/rapp-manager-application/src/main/resources/application.yaml b/rapp-manager-application/src/main/resources/application.yaml index 2a8489a..7e6679a 100755 --- a/rapp-manager-application/src/main/resources/application.yaml +++ b/rapp-manager-application/src/main/resources/application.yaml @@ -1,20 +1,20 @@ rappmanager: csarlocation: src/test/resources/csar acm: - baseurl: http://localhost:30442/onap/policy/clamp/acm/v2/ + baseurl: http://localhost:55562/onap/policy/clamp/acm/v2/ username: runtimeUser password: zb!XztG34 maxRetries: 10 retryInterval: 2 #seconds sme: - baseurl: http://localhost:56571 + baseurl: http://10.101.3.69:31575 providerBasePath: /api-provider-management/v1/ invokerBasePath: /api-invoker-management/v1/ publishApiBasePath: /published-apis/v1/ maxRetries: 3 retryInterval: 2 #seconds dme: - baseurl: http://localhost:63475 + baseurl: http://localhost:55626 rapps: env: diff --git a/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappControllerTest.java b/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappControllerTest.java index 530f17c..0cd6de1 100644 --- a/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappControllerTest.java +++ b/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappControllerTest.java @@ -137,6 +137,12 @@ class RappControllerTest { .andExpect(status().isBadRequest()); } + @Test + void testCreateInvalidMultipartRapp() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.multipart("/rapps/{rapp_id}", UUID.randomUUID())) + .andExpect(status().isBadRequest()); + } + @Test void testPrimeRapp() throws Exception { UUID rappId = UUID.randomUUID(); @@ -167,6 +173,23 @@ class RappControllerTest { .andExpect(status().isNotFound()); } + @ParameterizedTest + @ValueSource(strings = {"", " ", "{}", "{asdasd}", "{ \"primeOrder\": \"INVALID\"}", "{ \"primeOrder\": \"\"}"}) + void testPrimeRappInvalidPayload(String payload) throws Exception { + UUID rappId = UUID.randomUUID(); + Rapp rapp = Rapp.builder().rappId(rappId).name(String.valueOf(rappId)).packageName(validRappFile) + .packageLocation(validCsarFileLocation).state(RappState.COMMISSIONED).build(); + AsdMetadata asdMetadata = new AsdMetadata(); + asdMetadata.setDescriptorId(UUID.randomUUID().toString()); + asdMetadata.setDescriptorInvariantId(UUID.randomUUID().toString()); + asdMetadata.setDeploymentItems(List.of()); + rapp.setAsdMetadata(asdMetadata); + rappCacheService.putRapp(rapp); + mockMvc.perform(MockMvcRequestBuilders.put("/rapps/{rapp_id}", rappId).contentType(MediaType.APPLICATION_JSON) + .content(payload)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()); + } + @Test void testDeprimeRapp() throws Exception { UUID rappId = UUID.randomUUID(); diff --git a/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappInstanceControllerTest.java b/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappInstanceControllerTest.java index 35e1c39..46d06c5 100644 --- a/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappInstanceControllerTest.java +++ b/rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappInstanceControllerTest.java @@ -29,12 +29,15 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.oransc.rappmanager.acm.service.AcmDeployer; import org.oransc.rappmanager.dme.service.DmeDeployer; import org.oransc.rappmanager.models.cache.RappCacheService; import org.oransc.rappmanager.models.rapp.Rapp; import org.oransc.rappmanager.models.rapp.RappState; import org.oransc.rappmanager.models.rappinstance.DeployOrder; +import org.oransc.rappmanager.models.rappinstance.RappACMInstance; import org.oransc.rappmanager.models.rappinstance.RappInstance; import org.oransc.rappmanager.models.rappinstance.RappInstanceDeployOrder; import org.oransc.rappmanager.models.rappinstance.RappInstanceState; @@ -105,6 +108,9 @@ class RappInstanceControllerTest { UUID rappInstanceId = UUID.randomUUID(); RappInstance rappInstance = new RappInstance(); rappInstance.setRappInstanceId(rappInstanceId); + RappACMInstance rappACMInstance = new RappACMInstance(); + rappACMInstance.setInstance("test-instance"); + rappInstance.setAcm(rappACMInstance); rappInstance.setState(RappInstanceState.UNDEPLOYED); Rapp rapp = Rapp.builder().rappId(rappId).name(String.valueOf(rappId)).packageName(validRappFile) .packageLocation(validCsarFileLocation).state(RappState.PRIMED).build(); @@ -120,6 +126,9 @@ class RappInstanceControllerTest { void testCreateRappInstanceFailure() throws Exception { RappInstance rappInstance = new RappInstance(); rappInstance.setRappInstanceId(UUID.randomUUID()); + RappACMInstance rappACMInstance = new RappACMInstance(); + rappACMInstance.setInstance("test-instance"); + rappInstance.setAcm(rappACMInstance); rappInstance.setState(RappInstanceState.UNDEPLOYED); mockMvc.perform(MockMvcRequestBuilders.post("/rapps/{rapp_id}/instance", UUID.randomUUID()) .contentType(MediaType.APPLICATION_JSON) @@ -156,6 +165,28 @@ class RappInstanceControllerTest { .andExpect(status().isNotFound()); } + @ParameterizedTest + @ValueSource(strings = {"", " ", "{}", "{\"acm\":\"\"}", "{\"sme\":\"\"}", "{\"dme\":\"\"}", "{\"acm\":{}}", + "{\"sme\":{}}", "{\"dme\":{}}", "{\"acm\":{\"instance\":\"\"}}", "{\"sme\":{\"providerFunction\":\"\"}}", + "{\"dme\":{\"infoTypesProducer\":\"\"}}"}) + void testCreateRappInstanceInvalidPayload(String payload) throws Exception { + UUID rappId = UUID.randomUUID(); + Rapp rapp = Rapp.builder().rappId(rappId).name(String.valueOf(rappId)).packageName(validRappFile) + .packageLocation(validCsarFileLocation).state(RappState.PRIMED).build(); + rappCacheService.putRapp(rapp); + mockMvc.perform( + MockMvcRequestBuilders.post("/rapps/{rapp_id}/instance", rappId).contentType(MediaType.APPLICATION_JSON) + .content(payload)).andExpect(status().isBadRequest()).andExpect(jsonPath("$.message").exists()); + } + + @ParameterizedTest + @ValueSource(strings = {"123", "asdasd", "non-uuid-string"}) + void testGetRappInstanceInvalidInstanceParameter(String rAppInstanceId) throws Exception { + UUID rappId = UUID.randomUUID(); + mockMvc.perform(MockMvcRequestBuilders.get("/rapps/{rapp_id}/instance/{instance_id}", rappId, rAppInstanceId)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.message").exists()); + } + @Test void testDeployRappInstance() throws Exception { UUID rappId = UUID.randomUUID(); @@ -206,6 +237,33 @@ class RappInstanceControllerTest { .andExpect(status().isNotFound()); } + @ParameterizedTest + @ValueSource(strings = {"", " ", "{}", "{asdasd}", "{ \"deployOrder\": \"INVALID\"}", "{ \"deployOrder\": \"\"}"}) + void testDeployRappInstanceInvalidPayload(String payload) throws Exception { + UUID rappId = UUID.randomUUID(); + UUID rappInstanceId = UUID.randomUUID(); + Rapp rapp = getRapp(rappId, rappInstanceId); + rappCacheService.putRapp(rapp); + rappInstanceStateMachine.onboardRappInstance(rappInstanceId); + RappInstanceDeployOrder rappInstanceDeployOrder = new RappInstanceDeployOrder(); + rappInstanceDeployOrder.setDeployOrder(DeployOrder.DEPLOY); + when(acmDeployer.deployRappInstance(any(), any())).thenReturn(true); + when(smeDeployer.deployRappInstance(any(), any())).thenReturn(true); + when(dmeDeployer.deployRappInstance(any(), any())).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.put("/rapps/{rapp_id}/instance/{instance_id}", rappId, rappInstanceId) + .contentType(MediaType.APPLICATION_JSON).content(payload)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.message").exists()); + } + + @ParameterizedTest + @ValueSource(strings = {"123", "asdasd", "non-uuid-string"}) + void testDeployRappInstanceInvalidInstanceParameter(String rAppInstanceId) throws Exception { + UUID rappId = UUID.randomUUID(); + mockMvc.perform(MockMvcRequestBuilders.put("/rapps/{rapp_id}/instance/{instance_id}", rappId, rAppInstanceId) + .contentType(MediaType.APPLICATION_JSON).content("{}")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.message").exists()); + } + @Test void testUndeployRappInstance() throws Exception { UUID rappId = UUID.randomUUID(); @@ -293,6 +351,15 @@ class RappInstanceControllerTest { .andExpect(status().isNotFound()); } + @ParameterizedTest + @ValueSource(strings = {"123", "asdasd", "non-uuid-string"}) + void testDeleteRappInstanceInvalidInstanceParameter(String rAppInstanceId) throws Exception { + UUID rappId = UUID.randomUUID(); + mockMvc.perform( + MockMvcRequestBuilders.delete("/rapps/{rapp_id}/instance/{instance_id}", rappId, rAppInstanceId)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.message").exists()); + } + Rapp getRapp(UUID rappId, UUID rappInstanceId) { RappInstance rappInstance = new RappInstance(); rappInstance.setRappInstanceId(rappInstanceId); diff --git a/rapp-manager-models/pom.xml b/rapp-manager-models/pom.xml index 20c5f53..f300495 100755 --- a/rapp-manager-models/pom.xml +++ b/rapp-manager-models/pom.xml @@ -93,6 +93,12 @@ guava ${guava.version} + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet.api.version} + provided + org.springframework.boot spring-boot-starter-test diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/exception/ExceptionControllerHandler.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/exception/ExceptionControllerHandler.java deleted file mode 100644 index a4ebb64..0000000 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/exception/ExceptionControllerHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * ============LICENSE_START====================================================================== - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. - * =============================================================================================== - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END======================================================================== - */ - -package org.oransc.rappmanager.models.exception; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@RestControllerAdvice -public class ExceptionControllerHandler { - - @ExceptionHandler(RappHandlerException.class) - public ResponseEntity handleRappHandlerException(RappHandlerException rappHandlerException) { - return ResponseEntity.status(rappHandlerException.getStatusCode()) - .body(new ErrorResponse(rappHandlerException.getMessage())); - } - - @ExceptionHandler(RappValidationException.class) - public ResponseEntity handleRappValidationException( - RappValidationException rappValidationException) { - return ResponseEntity.status(rappValidationException.getStatusCode()) - .body(new ErrorResponse(rappValidationException.getMessage())); - } -} diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rapp/RappPrimeOrder.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rapp/RappPrimeOrder.java index 17a18d8..3586def 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rapp/RappPrimeOrder.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rapp/RappPrimeOrder.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,13 @@ package org.oransc.rappmanager.models.rapp; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class RappPrimeOrder { + @NotNull PrimeOrder primeOrder; } diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappACMInstance.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappACMInstance.java index 08cf068..bfa89ae 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappACMInstance.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappACMInstance.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,14 @@ package org.oransc.rappmanager.models.rappinstance; +import jakarta.validation.constraints.NotBlank; import java.util.UUID; import lombok.Data; @Data public class RappACMInstance { + @NotBlank String instance; UUID acmInstanceId; diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappDMEInstance.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappDMEInstance.java index b10543b..98b4aed 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappDMEInstance.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappDMEInstance.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,27 @@ package org.oransc.rappmanager.models.rappinstance; +import jakarta.validation.GroupSequence; import java.util.Set; import lombok.Data; +import org.oransc.rappmanager.models.validators.AtleastOneFieldShouldNotBeNull; +import org.oransc.rappmanager.models.validators.ClassLevelValidatorGroup; +import org.oransc.rappmanager.models.validators.FieldLevelValidatorGroup; +import org.oransc.rappmanager.models.validators.NullOrNotEmpty; @Data +@AtleastOneFieldShouldNotBeNull(fields = {"infoTypesProducer", "infoProducer", "infoTypeConsumer", "infoConsumer"}, + message = "At least one of the fields infoTypesProducer, infoProducer, infoTypeConsumer or infoConsumer must be provided", + groups = ClassLevelValidatorGroup.class) +@GroupSequence({RappDMEInstance.class, ClassLevelValidatorGroup.class, FieldLevelValidatorGroup.class}) public class RappDMEInstance { + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) Set infoTypesProducer; + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String infoProducer; + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String infoTypeConsumer; + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String infoConsumer; } diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstance.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstance.java index 8d14e11..7348ef7 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstance.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstance.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,18 +19,25 @@ package org.oransc.rappmanager.models.rappinstance; +import jakarta.validation.Valid; import java.util.UUID; import java.util.stream.Stream; import lombok.Data; +import org.oransc.rappmanager.models.validators.AtleastOneFieldShouldNotBeNull; @Data +@AtleastOneFieldShouldNotBeNull(fields = {"acm", "sme", "dme"}, + message = "At least one of the fields 'acm', 'sme', or 'dme' must not be null") public class RappInstance { UUID rappInstanceId = UUID.randomUUID(); RappInstanceState state = RappInstanceState.UNDEPLOYED; String reason; + @Valid RappACMInstance acm; + @Valid RappSMEInstance sme; + @Valid RappDMEInstance dme; public boolean isSMEEnabled() { diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstanceDeployOrder.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstanceDeployOrder.java index 7f8808c..994d8de 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstanceDeployOrder.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstanceDeployOrder.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,12 @@ package org.oransc.rappmanager.models.rappinstance; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class RappInstanceDeployOrder { + @NotNull DeployOrder deployOrder; } diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappSMEInstance.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappSMEInstance.java index 7d76045..04c4fa9 100644 --- a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappSMEInstance.java +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappSMEInstance.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START====================================================================== * Copyright (C) 2023 Nordix Foundation. All rights reserved. - * Copyright (C) 2023-2024 OpenInfra Foundation Europe. All rights reserved. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * =============================================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,28 @@ package org.oransc.rappmanager.models.rappinstance; +import jakarta.validation.GroupSequence; import java.util.List; import lombok.Data; +import org.oransc.rappmanager.models.validators.AtleastOneFieldShouldNotBeNull; +import org.oransc.rappmanager.models.validators.ClassLevelValidatorGroup; +import org.oransc.rappmanager.models.validators.FieldLevelValidatorGroup; +import org.oransc.rappmanager.models.validators.NullOrNotEmpty; @Data +@AtleastOneFieldShouldNotBeNull(fields = {"providerFunction", "serviceApis", "invokers"}, + message = "At least one of the fields providerFunction, serviceApis or invokers must be provided", + groups = ClassLevelValidatorGroup.class) +@GroupSequence({RappSMEInstance.class, ClassLevelValidatorGroup.class, FieldLevelValidatorGroup.class}) public class RappSMEInstance { + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String providerFunction; List providerFunctionIds; + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String serviceApis; List serviceApiIds; + @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class) String invokers; List invokerIds; diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNull.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNull.java new file mode 100644 index 0000000..49c5154 --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNull.java @@ -0,0 +1,42 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.models.validators; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = AtleastOneFieldShouldNotBeNullValidator.class) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AtleastOneFieldShouldNotBeNull { + + String[] fields(); + + String message() default "At least one of these fields must not be null"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidator.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidator.java new file mode 100644 index 0000000..36dc28c --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidator.java @@ -0,0 +1,54 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.models.validators; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.lang.reflect.Field; +import org.springframework.util.ReflectionUtils; + +public class AtleastOneFieldShouldNotBeNullValidator + implements ConstraintValidator { + + private String[] fields; + + @Override + public void initialize(AtleastOneFieldShouldNotBeNull constraintAnnotation) { + this.fields = constraintAnnotation.fields(); + } + + @Override + public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) { + if (obj == null) { + return false; + } + try { + for (String field : fields) { + Field declaredField = obj.getClass().getDeclaredField(field); + ReflectionUtils.makeAccessible(declaredField); + if (declaredField.get(obj) != null) { + return true; + } + } + } catch (NoSuchFieldException | IllegalAccessException e) { + return false; + } + return false; + } +} diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/ClassLevelValidatorGroup.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/ClassLevelValidatorGroup.java new file mode 100644 index 0000000..8bfc27e --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/ClassLevelValidatorGroup.java @@ -0,0 +1,20 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ +package org.oransc.rappmanager.models.validators; + +public interface ClassLevelValidatorGroup { } diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/FieldLevelValidatorGroup.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/FieldLevelValidatorGroup.java new file mode 100644 index 0000000..8673fb3 --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/FieldLevelValidatorGroup.java @@ -0,0 +1,20 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ +package org.oransc.rappmanager.models.validators; + +public interface FieldLevelValidatorGroup { } diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmpty.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmpty.java new file mode 100644 index 0000000..e520b95 --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmpty.java @@ -0,0 +1,40 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.models.validators; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = NullOrNotEmptyValidator.class) +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NullOrNotEmpty { + + String message() default "Field must be null or not empty"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidator.java b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidator.java new file mode 100644 index 0000000..e8765da --- /dev/null +++ b/rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidator.java @@ -0,0 +1,31 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.models.validators; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.apache.commons.lang3.ObjectUtils; + +public class NullOrNotEmptyValidator implements ConstraintValidator { + + @Override + public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) { + return obj == null || !ObjectUtils.isEmpty(obj); + } +} diff --git a/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidatorTest.java b/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidatorTest.java new file mode 100644 index 0000000..5e91722 --- /dev/null +++ b/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidatorTest.java @@ -0,0 +1,109 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ + +package org.oransc.rappmanager.models.validators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.validation.Payload; +import org.junit.jupiter.api.Test; + +class AtleastOneFieldShouldNotBeNullValidatorTest { + + private final AtleastOneFieldShouldNotBeNullValidator validator = new AtleastOneFieldShouldNotBeNullValidator(); + + @Test + void testValidationWithAllNullFields() { + validator.initialize(getAnnotation()); + Object testObject = new Object() { + public final String acm = null; + public final String sme = null; + public final String dme = null; + }; + assertFalse(validator.isValid(testObject, null)); + } + + @Test + void testValidationWithNonNullFields() { + validator.initialize(getAnnotation()); + Object testObjectAcmNotNull = new Object() { + public final String acm = "data"; + public final String sme = null; + public final String dme = null; + }; + Object testObjectSmeNotNull = new Object() { + public final String acm = null; + public final String sme = "data"; + public final String dme = null; + }; + Object testObjectDmeNotNull = new Object() { + public final String acm = null; + public final String sme = null; + public final String dme = "data"; + }; + assertTrue(validator.isValid(testObjectAcmNotNull, null)); + assertTrue(validator.isValid(testObjectSmeNotNull, null)); + assertTrue(validator.isValid(testObjectDmeNotNull, null)); + } + + @Test + void testValidationWithNullObject() { + validator.initialize(getAnnotation()); + assertFalse(validator.isValid(null, null)); + } + + @Test + void testValidationInvalidFieldConfiguration() { + validator.initialize(getAnnotation()); + Object testObjectInvalidField = new Object() { + public final String acm = null; + public final String sme = null; + }; + assertFalse(validator.isValid(testObjectInvalidField, null)); + } + + AtleastOneFieldShouldNotBeNull getAnnotation() { + return new AtleastOneFieldShouldNotBeNull() { + @Override + public Class annotationType() { + return AtleastOneFieldShouldNotBeNull.class; + } + + @Override + public String[] fields() { + return new String[] {"acm", "sme", "dme"}; + } + + @Override + public String message() { + return "At least one field must not be null"; + } + + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + }; + } +} \ No newline at end of file diff --git a/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidatorTest.java b/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidatorTest.java new file mode 100644 index 0000000..7118291 --- /dev/null +++ b/rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidatorTest.java @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START====================================================================== + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * =============================================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END======================================================================== + */ +package org.oransc.rappmanager.models.validators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class NullOrNotEmptyValidatorTest { + + NullOrNotEmptyValidator validator = new NullOrNotEmptyValidator(); + + @Test + void testValidationNullStringField() { + assertTrue(validator.isValid(null, null)); + } + + @Test + void testValidationNonEmptyStringField() { + String testString = "ValidString"; + assertTrue(validator.isValid(testString, null)); + } + + @Test + void testValidationEmptyStringField() { + String testString = ""; + assertFalse(validator.isValid(testString, null)); + } + + @Test + void testValidationNullCollectionField() { + List testList = null; + assertTrue(validator.isValid(testList, null)); + } + + @Test + void testValidationNonEmptyCollectionField() { + List testList = List.of("item1", "item2"); + assertTrue(validator.isValid(testList, null)); + } + + @Test + void testValidationEmptyCollectionField() { + assertFalse(validator.isValid(List.of(), null)); + } +}