Fix 'Changing VFC version on template wipes previously assigned property values based...
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / merge / property / PropertyValueMerger.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.components.merge.property;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.stream.Collectors;
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.commons.lang3.tuple.ImmutablePair;
29 import org.openecomp.sdc.be.model.DataTypeDefinition;
30 import org.openecomp.sdc.be.model.PropertyDefinition;
31 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
32 import org.openecomp.sdc.be.utils.TypeUtils;
33 import org.springframework.stereotype.Component;
34
35 @Component
36 public class PropertyValueMerger {
37
38     static boolean isEmptyValue(Object val) {
39         return val == null || val instanceof String && StringUtils.isEmpty((String) val) || val instanceof Map && ((Map<?, ?>) val).isEmpty()
40             || val instanceof List && ((List<?>) val).isEmpty();
41     }
42
43     @SuppressWarnings("unchecked")
44     /**
45      * merges property value oldVal into property value newVal recursively
46      * @param oldVal - cannot be {@code Null}
47      */
48     protected Object merge(Object oldVal, Object newVal, List<String> inputNamesToMerge, String type, String innerType,
49                            Map<String, DataTypeDefinition> dataTypes) {
50         if (isEmptyValue(newVal)) {
51             return removeUnwantedGetInputValues(oldVal, inputNamesToMerge);
52         }
53         if (isMapTypeValues(oldVal, newVal)) {
54             return mergeMapValue((Map<String, Object>) oldVal, (Map<String, Object>) newVal, inputNamesToMerge, type, innerType, dataTypes);
55         }
56         if (isListTypeValues(oldVal, newVal)) {
57             return mergeListValue((List<Object>) oldVal, (List<Object>) newVal, inputNamesToMerge, innerType, dataTypes);
58         }
59         if (isSameTypeValues(oldVal, newVal)) {
60             return mergeScalarValue(oldVal, newVal);
61         }
62         return newVal;
63     }
64
65     private Map<String, Object> mergeMapValue(Map<String, Object> oldValMap, Map<String, Object> newValMap, List<String> inputNamesToMerge,
66                                               String type, String innertType, Map<String, DataTypeDefinition> dataTypes) {
67         mergeEntriesExistInOldValue(oldValMap, newValMap, inputNamesToMerge, type, innertType, dataTypes);//continue the recursion
68         if (type != null && !type.equals("map") && !type.equals("json")) {
69             setOldEntriesNotExistInNewValue(oldValMap, newValMap, inputNamesToMerge);
70         }
71         return newValMap;
72     }
73
74     private void mergeEntriesExistInOldValue(Map<String, Object> oldValMap, Map<String, Object> newValMap, List<String> inputNamesToMerge,
75                                              String type, String innerType, Map<String, DataTypeDefinition> dataTypes) {
76         for (Map.Entry<String, Object> newValEntry : newValMap.entrySet()) {
77             Object oldVal = oldValMap.get(newValEntry.getKey());
78             if (oldVal != null) {
79                 ImmutablePair<String, String> types = getTypeAndInnerTypePair(newValEntry.getKey(), type, innerType, dataTypes);
80                 newValMap.put(newValEntry.getKey(),
81                     merge(oldVal, newValEntry.getValue(), inputNamesToMerge, types.getLeft(), types.getRight(), dataTypes));
82             }
83         }
84     }
85
86     private void setOldEntriesNotExistInNewValue(Map<String, Object> oldVal, Map<String, Object> newVal, List<String> getInputNamesToMerge) {
87         for (Map.Entry<String, Object> oldValEntry : oldVal.entrySet()) {
88             if (!isInputEntry(oldValEntry) || isInputToMerge(getInputNamesToMerge, oldValEntry)) {
89                 Object oldValObj = oldValEntry.getValue();
90                 newVal.computeIfAbsent(oldValEntry.getKey(), key -> removeUnwantedGetInputValues(oldValObj, getInputNamesToMerge));
91             }
92         }
93     }
94
95     private ImmutablePair<String, String> getTypeAndInnerTypePair(String propName, String type, String innerType,
96                                                                   Map<String, DataTypeDefinition> dataTypes) {
97         if (type == null || (ToscaPropertyType.isScalarType(type) && ToscaPropertyType.isScalarType(innerType))) {
98             return ImmutablePair.of(innerType, null);
99         }
100         String newInnerType = null;
101         DataTypeDefinition innerTypeDef = dataTypes.get(type);
102         if (innerTypeDef != null) {
103             List<PropertyDefinition> properties = innerTypeDef.getProperties();
104             if (properties != null) {
105                 Optional<PropertyDefinition> optionalProperty = findProperty(properties, propName);
106                 innerType = optionalProperty.map(PropertyDefinition::getType).orElse(innerType);
107                 newInnerType = optionalProperty.map(PropertyDefinition::getSchemaType).orElse(null);
108             }
109         }
110         return ImmutablePair.of(innerType, newInnerType);
111     }
112
113     private Optional<PropertyDefinition> findProperty(List<PropertyDefinition> properties, String propName) {
114         return properties.stream().filter(p -> propName.equals(p.getName())).findFirst();
115     }
116
117     private List<Object> mergeListValue(List<Object> oldVal, List<Object> newVal, List<String> inputNamesToMerge, String innerType,
118                                         Map<String, DataTypeDefinition> dataTypes) {
119         List<Object> mergedList = newVal;
120         if (oldVal.size() == newVal.size()) {
121             mergedList = mergeLists(oldVal, newVal, inputNamesToMerge, innerType, dataTypes);
122         }
123         return mergedList;
124     }
125
126     private List<Object> mergeLists(List<Object> oldVal, List<Object> newVal, List<String> inputNamesToMerge, String innerType,
127                                     Map<String, DataTypeDefinition> dataTypes) {
128         int minListSize = Math.min(oldVal.size(), newVal.size());
129         List<Object> mergedList = new ArrayList<>();
130         for (int i = 0; i < minListSize; i++) {
131             Object mergedVal = merge(oldVal.get(i), newVal.get(i), inputNamesToMerge, innerType, null, dataTypes);
132             mergedList.add(mergedVal);
133         }
134         return mergedList;
135     }
136
137     private Object mergeScalarValue(Object oldVal, Object newVal) {
138         return isEmptyValue(newVal) ? oldVal : newVal;
139     }
140
141     @SuppressWarnings("unchecked")
142     Object removeUnwantedGetInputValues(Object val, List<String> inputNamesToMerge) {
143         if (val instanceof Map) {
144             return removeUnwantedGetInputValues((Map<String, Object>) val, inputNamesToMerge);
145         }
146         if (val instanceof List) {
147             return removeUnwantedGetInputValues((List<Object>) val, inputNamesToMerge);
148         }
149         return val;
150     }
151
152     private List<Object> removeUnwantedGetInputValues(List<Object> listVal, List<String> inputNamesToMerge) {
153         return listVal.stream().map(val -> removeUnwantedGetInputValues(val, inputNamesToMerge)).collect(Collectors.toList());
154     }
155
156     private Map<String, Object> removeUnwantedGetInputValues(Map<String, Object> val, List<String> inputNamesToMerge) {
157         return val.entrySet().stream().filter(entry -> !isInputEntry(entry) || isInputToMerge(inputNamesToMerge, entry))
158             .collect(Collectors.toMap(Map.Entry::getKey, entry -> removeUnwantedGetInputValues(entry.getValue(), inputNamesToMerge)));
159     }
160
161     private boolean isInputToMerge(List<String> inputNamesToMerge, Map.Entry<String, Object> entry) {
162         return inputNamesToMerge.contains(retrieveInputName(entry.getValue()));
163     }
164
165     private boolean isMapTypeValues(Object oldVal, Object newVal) {
166         return newVal instanceof Map && oldVal instanceof Map;
167     }
168
169     private boolean isListTypeValues(Object oldVal, Object newVal) {
170         return newVal instanceof List && oldVal instanceof List;
171     }
172
173     private boolean isSameTypeValues(Object oldVal, Object newVal) {
174         return oldVal.getClass().equals(newVal.getClass());
175     }
176
177     private String retrieveInputName(Object inputValue) {
178         return inputValue instanceof List ? (String) ((List<?>) inputValue).get(0) : (String) inputValue;
179     }
180
181     protected boolean isInputEntry(Map.Entry<String, Object> oldValEntry) {
182         return oldValEntry.getKey().equals(TypeUtils.ToscaTagNamesEnum.GET_INPUT.getElementName());
183     }
184 }