Request validation added 10/14710/1
authoraravind.est <aravindhan.a@est.tech>
Thu, 10 Jul 2025 17:37:03 +0000 (18:37 +0100)
committeraravind.est <aravindhan.a@est.tech>
Thu, 10 Jul 2025 17:51:22 +0000 (18:51 +0100)
Request parameters and payload validation added.

Issue-ID: NONRTRIC-1080
Change-Id: I646eee69d18fb74f2806610d1e123a13dbc2cd0e
Signed-off-by: aravind.est <aravindhan.a@est.tech>
24 files changed:
pom.xml
rapp-manager-application/pom.xml
rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/ExceptionControllerHandler.java [new file with mode: 0644]
rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappController.java
rapp-manager-application/src/main/java/org/oransc/rappmanager/rest/RappInstanceController.java
rapp-manager-application/src/main/resources/application.yaml
rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappControllerTest.java
rapp-manager-application/src/test/java/org/oransc/rappmanager/rest/RappInstanceControllerTest.java
rapp-manager-models/pom.xml
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/exception/ExceptionControllerHandler.java [deleted file]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rapp/RappPrimeOrder.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappACMInstance.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappDMEInstance.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstance.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappInstanceDeployOrder.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/rappinstance/RappSMEInstance.java
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNull.java [new file with mode: 0644]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidator.java [new file with mode: 0644]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/ClassLevelValidatorGroup.java [new file with mode: 0644]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/FieldLevelValidatorGroup.java [new file with mode: 0644]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmpty.java [new file with mode: 0644]
rapp-manager-models/src/main/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidator.java [new file with mode: 0644]
rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/AtleastOneFieldShouldNotBeNullValidatorTest.java [new file with mode: 0644]
rapp-manager-models/src/test/java/org/oransc/rappmanager/models/validators/NullOrNotEmptyValidatorTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index d9922d9..2e2ab70 100755 (executable)
--- a/pom.xml
+++ b/pom.xml
@@ -56,8 +56,6 @@
         <openapi.jackson.databind.nullable.version>0.2.6</openapi.jackson.databind.nullable.version>
         <spring.statemachine.version>4.0.1</spring.statemachine.version>
         <spring.context.version>6.2.8</spring.context.version>
-        <jakarta.validation.api.version>3.1.1</jakarta.validation.api.version>
-        <jakarta.annotation.api.version>3.0.0</jakarta.annotation.api.version>
         <jakarta.servlet.api.version>6.1.0</jakarta.servlet.api.version>
         <io.swagger.version>2.2.34</io.swagger.version>
         <slf4j.version>2.1.0-alpha1</slf4j.version>
index 09ee2b5..cc571f9 100755 (executable)
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-cache</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-configuration-processor</artifactId>
             <artifactId>jackson-databind-nullable</artifactId>
             <version>${openapi.jackson.databind.nullable.version}</version>
         </dependency>
-        <!-- Bean Validation API support -->
-        <dependency>
-            <groupId>jakarta.validation</groupId>
-            <artifactId>jakarta.validation-api</artifactId>
-            <version>${jakarta.validation.api.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>jakarta.annotation</groupId>
-            <artifactId>jakarta.annotation-api</artifactId>
-            <version>${jakarta.annotation.api.version}</version>
-        </dependency>
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-annotations</artifactId>
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 (file)
index 0000000..7f76d1a
--- /dev/null
@@ -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<ErrorResponse> handleRappHandlerException(RappHandlerException rappHandlerException) {
+        return ResponseEntity.status(rappHandlerException.getStatusCode())
+                       .body(new ErrorResponse(rappHandlerException.getMessage()));
+    }
+
+    @ExceptionHandler(RappValidationException.class)
+    public ResponseEntity<ErrorResponse> handleRappValidationException(
+            RappValidationException rappValidationException) {
+        return ResponseEntity.status(rappValidationException.getStatusCode())
+                       .body(new ErrorResponse(rappValidationException.getMessage()));
+    }
+
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public ResponseEntity<ErrorResponse> 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<ErrorResponse> 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<ErrorResponse> 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<ErrorResponse> 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())));
+    }
+}
index a5e698d..a8335f6 100644 (file)
@@ -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<Rapp> 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<String> 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())
index ee15d82..4d2d33a 100644 (file)
@@ -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<RappInstance> 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<RappInstance> 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<String> 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<String> 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,
index 2a8489a..7e6679a 100755 (executable)
@@ -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:
index 530f17c..0cd6de1 100644 (file)
@@ -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();
index 35e1c39..46d06c5 100644 (file)
@@ -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);
index 20c5f53..f300495 100755 (executable)
             <artifactId>guava</artifactId>
             <version>${guava.version}</version>
         </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <version>${jakarta.servlet.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
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 (file)
index a4ebb64..0000000
+++ /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<ErrorResponse> handleRappHandlerException(RappHandlerException rappHandlerException) {
-        return ResponseEntity.status(rappHandlerException.getStatusCode())
-                       .body(new ErrorResponse(rappHandlerException.getMessage()));
-    }
-
-    @ExceptionHandler(RappValidationException.class)
-    public ResponseEntity<ErrorResponse> handleRappValidationException(
-            RappValidationException rappValidationException) {
-        return ResponseEntity.status(rappValidationException.getStatusCode())
-                       .body(new ErrorResponse(rappValidationException.getMessage()));
-    }
-}
index 17a18d8..3586def 100644 (file)
@@ -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.
 
 package org.oransc.rappmanager.models.rapp;
 
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Data
 public class RappPrimeOrder {
 
+    @NotNull
     PrimeOrder primeOrder;
 
 }
index 08cf068..bfa89ae 100644 (file)
@@ -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.
 
 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;
 
index b10543b..98b4aed 100644 (file)
@@ -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.
 
 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<String> infoTypesProducer;
+    @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class)
     String infoProducer;
+    @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class)
     String infoTypeConsumer;
+    @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class)
     String infoConsumer;
 }
index 8d14e11..7348ef7 100644 (file)
@@ -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.
 
 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() {
index 7f8808c..994d8de 100644 (file)
@@ -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.
 
 package org.oransc.rappmanager.models.rappinstance;
 
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 @Data
 public class RappInstanceDeployOrder {
 
+    @NotNull
     DeployOrder deployOrder;
 }
index 7d76045..04c4fa9 100644 (file)
@@ -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.
 
 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<String> providerFunctionIds;
+    @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class)
     String serviceApis;
     List<String> serviceApiIds;
+    @NullOrNotEmpty(groups = FieldLevelValidatorGroup.class)
     String invokers;
     List<String> 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 (file)
index 0000000..49c5154
--- /dev/null
@@ -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<? extends Payload>[] 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 (file)
index 0000000..36dc28c
--- /dev/null
@@ -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<AtleastOneFieldShouldNotBeNull, Object> {
+
+    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 (file)
index 0000000..8bfc27e
--- /dev/null
@@ -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 (file)
index 0000000..8673fb3
--- /dev/null
@@ -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 (file)
index 0000000..e520b95
--- /dev/null
@@ -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<? extends Payload>[] 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 (file)
index 0000000..e8765da
--- /dev/null
@@ -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<NullOrNotEmpty, Object> {
+
+    @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 (file)
index 0000000..5e91722
--- /dev/null
@@ -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<? extends java.lang.annotation.Annotation> 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<? extends Payload>[] 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 (file)
index 0000000..7118291
--- /dev/null
@@ -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));
+    }
+}