2ad53cbe7d6bb4209fec824907ae6c177ab0e585
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2025-2026 OpenInfra Foundation Europe
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.impl.data.policyexecutor;
22
23 import static java.util.Collections.emptyMap;
24 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
25 import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE;
26
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import lombok.RequiredArgsConstructor;
31 import lombok.extern.slf4j.Slf4j;
32 import org.onap.cps.ncmp.api.data.models.OperationType;
33 import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
34 import org.onap.cps.ncmp.impl.provmns.ParameterHelper;
35 import org.onap.cps.ncmp.impl.provmns.RequestParameters;
36 import org.onap.cps.ncmp.impl.provmns.model.PatchItem;
37 import org.onap.cps.utils.JsonObjectMapper;
38 import org.springframework.http.HttpStatus;
39 import org.springframework.stereotype.Service;
40
41 @Slf4j
42 @Service
43 @RequiredArgsConstructor
44 public class OperationDetailsFactory {
45
46     public static final OperationDetails DELETE_OPERATION_DETAILS  = new OperationDetails("delete", "", emptyMap());
47
48     private static final String ATTRIBUTE_NAME_SEPARATOR = "/";
49     private static final String REGEX_FOR_LEADING_AND_TRAILING_SEPARATORS = "(^/)|(/$)";
50
51     private final JsonObjectMapper jsonObjectMapper;
52
53     /**
54      * Create OperationDetails object from ProvMnS request details.
55      *
56      * @param requestParameters request parameters including uri-ldn-first-part, className and id
57      * @param patchItem provided request payload
58      * @return OperationDetails object
59      */
60     public OperationDetails buildOperationDetails(final RequestParameters requestParameters,
61                                                   final PatchItem patchItem) {
62         final OperationDetails operationDetails;
63         switch (patchItem.getOp()) {
64             case ADD:
65                 operationDetails = buildOperationDetailsForPatchItem(CREATE, requestParameters, patchItem);
66                 break;
67             case REPLACE:
68                 if (patchItem.getPath().contains("#/attributes")) {
69                     operationDetails = buildOperationDetailsForPatchItemWithHash(requestParameters, patchItem);
70                 } else {
71                     operationDetails = buildOperationDetailsForPatchItem(UPDATE, requestParameters, patchItem);
72                 }
73                 break;
74             case REMOVE:
75                 operationDetails = DELETE_OPERATION_DETAILS;
76                 break;
77             default:
78                 throw new ProvMnSException("PATCH", HttpStatus.UNPROCESSABLE_ENTITY,
79                     "Unsupported Patch Operation Type: " + patchItem.getOp().getValue(), patchItem.getOp().getValue());
80         }
81         return operationDetails;
82     }
83
84     /**
85      * Build a OperationDetails object from ProvMnS request details.
86      *
87      * @param operationType     Type of operation create, update.
88      * @param requestParameters request parameters including uri-ldn-first-part, className and id
89      * @param resourceAsObject  provided request payload
90      * @return OperationDetails object
91      */
92     public OperationDetails buildOperationDetails(final OperationType operationType,
93                                                   final RequestParameters requestParameters,
94                                                   final Object resourceAsObject) {
95         final ResourceObjectDetails resourceObjectDetails = createResourceObjectDetails(resourceAsObject,
96             requestParameters);
97         final OperationEntry operationEntry = new OperationEntry(resourceObjectDetails.id(),
98             resourceObjectDetails.attributes());
99         final Map<String, List<OperationEntry>> changeRequestAsMap =
100             Map.of(resourceObjectDetails.objectClass(), List.of(operationEntry));
101         final String targetIdentifier = ParameterHelper.extractParentFdn(requestParameters.fdn());
102         return new OperationDetails(operationType.getOperationName(), targetIdentifier, changeRequestAsMap);
103     }
104
105     /**
106      * Build OperationDetails for a specific patch item.
107      *
108      * @param operationType     the type of operation (CREATE, UPDATE)
109      * @param requestParameters request parameters including uri-ldn-first-part, className and id
110      * @param patchItem         the patch item containing operation details
111      * @return OperationDetails object for the patch item
112      */
113     public OperationDetails buildOperationDetailsForPatchItem(final OperationType operationType,
114                                                               final RequestParameters requestParameters,
115                                                               final PatchItem patchItem) {
116         final Map<String, Object> resourceAsObject = new HashMap<>(2);
117         resourceAsObject.put("id", requestParameters.id());
118         resourceAsObject.put("attributes", patchItem.getValue());
119         return buildOperationDetails(operationType, requestParameters, resourceAsObject);
120     }
121
122     private OperationDetails buildOperationDetailsForPatchItemWithHash(final RequestParameters requestParameters,
123                                                                        final PatchItem patchItem) {
124         final Map<String, Object> attributeHierarchyAsMap = createNestedMap(patchItem);
125         final OperationEntry operationEntry = new OperationEntry(requestParameters.id(), attributeHierarchyAsMap);
126         final String targetIdentifier = ParameterHelper.extractParentFdn(requestParameters.fdn());
127         final Map<String, List<OperationEntry>> operationEntriesPerObjectClass = new HashMap<>();
128         operationEntriesPerObjectClass.put(requestParameters.className(), List.of(operationEntry));
129         return new OperationDetails(UPDATE.getOperationName(), targetIdentifier, operationEntriesPerObjectClass);
130     }
131
132     @SuppressWarnings("unchecked")
133     private ResourceObjectDetails createResourceObjectDetails(final Object resourceAsObject,
134                                                               final RequestParameters requestParameters) {
135         final String resourceAsJson = jsonObjectMapper.asJsonString(resourceAsObject);
136         final Map<String, Object> resourceAsMap = jsonObjectMapper.convertJsonString(resourceAsJson, Map.class);
137         return new ResourceObjectDetails(requestParameters.id(),
138                                          requestParameters.className(),
139                                          resourceAsMap.get("attributes"));
140     }
141
142     private Map<String, Object> createNestedMap(final PatchItem patchItem) {
143         final Map<String, Object> attributeHierarchyMap = new HashMap<>();
144         Map<String, Object> currentLevel = attributeHierarchyMap;
145         final String[] attributeHierarchyNames = patchItem.getPath().split("#/attributes")[1]
146                 .replaceAll(REGEX_FOR_LEADING_AND_TRAILING_SEPARATORS, "")
147                 .split(ATTRIBUTE_NAME_SEPARATOR);
148         for (int level = 0; level < attributeHierarchyNames.length; level++) {
149             final String attributeName = attributeHierarchyNames[level];
150             if (isLastLevel(attributeHierarchyNames, level)) {
151                 currentLevel.put(attributeName, patchItem.getValue());
152             } else {
153                 final Map<String, Object> nextLevel = new HashMap<>();
154                 currentLevel.put(attributeName, nextLevel);
155                 currentLevel = nextLevel;
156             }
157         }
158         return attributeHierarchyMap;
159     }
160
161     private boolean isLastLevel(final String[] attributeNamesArray, final int level) {
162         return level == attributeNamesArray.length - 1;
163     }
164 }
165