Request parameters and payload validation added.
Issue-ID: NONRTRIC-1080
Change-Id: I646eee69d18fb74f2806610d1e123a13dbc2cd0e
Signed-off-by: aravind.est <aravindhan.a@est.tech>
<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>
<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>
--- /dev/null
+/*
+ * ============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())));
+ }
+}
package org.oransc.rappmanager.rest;
+import jakarta.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@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(),
@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())
/*-
* ============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.rest;
+import jakarta.validation.Valid;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@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);
@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 -> {
@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))
@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,
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:
.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();
.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();
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;
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();
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)
.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();
.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();
.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);
<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>
+++ /dev/null
-/*
- * ============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()));
- }
-}
/*-
* ============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;
}
/*-
* ============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;
/*-
* ============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;
}
/*-
* ============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() {
/*-
* ============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;
}
/*-
* ============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;
--- /dev/null
+/*-
+ * ============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 {};
+}
--- /dev/null
+/*-
+ * ============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;
+ }
+}
--- /dev/null
+/*-
+ * ============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 { }
--- /dev/null
+/*-
+ * ============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 { }
--- /dev/null
+/*-
+ * ============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 {};
+}
--- /dev/null
+/*-
+ * ============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);
+ }
+}
--- /dev/null
+/*-
+ * ============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
--- /dev/null
+/*-
+ * ============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));
+ }
+}