Sync Integ to Master
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / property / DefaultPropertyDecelerator.java
1 package org.openecomp.sdc.be.components.property;
2
3 import com.google.gson.Gson;
4 import fj.data.Either;
5 import org.apache.commons.collections.CollectionUtils;
6 import org.apache.commons.collections.MapUtils;
7 import org.json.simple.JSONObject;
8 import org.openecomp.sdc.be.dao.titan.TitanOperationStatus;
9 import org.openecomp.sdc.be.datatypes.elements.GetInputValueDataDefinition;
10 import org.openecomp.sdc.be.datatypes.elements.PropertiesOwner;
11 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
12 import org.openecomp.sdc.be.impl.ComponentsUtils;
13 import org.openecomp.sdc.be.model.Component;
14 import org.openecomp.sdc.be.model.ComponentInstancePropInput;
15 import org.openecomp.sdc.be.model.IComponentInstanceConnectedElement;
16 import org.openecomp.sdc.be.model.InputDefinition;
17 import org.openecomp.sdc.be.model.PropertyDefinition;
18 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
19 import org.openecomp.sdc.be.model.operations.impl.DaoStatusConverter;
20 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
21 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
22 import org.openecomp.sdc.exception.ResponseFormat;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25 import org.yaml.snakeyaml.Yaml;
26
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36
37 import static org.openecomp.sdc.common.api.Constants.GET_INPUT;
38
39 public abstract class DefaultPropertyDecelerator<PROPERTYOWNER extends PropertiesOwner, PROPERTYTYPE extends PropertyDataDefinition> implements PropertyDecelerator {
40
41     private static final Logger log = LoggerFactory.getLogger(DefaultPropertyDecelerator.class);
42     private static final short LOOP_PROTECTION_LEVEL = 10;
43     private final Gson gson = new Gson();
44     private ComponentsUtils componentsUtils;
45     private PropertyOperation propertyOperation;
46
47     public DefaultPropertyDecelerator(ComponentsUtils componentsUtils, PropertyOperation propertyOperation) {
48         this.componentsUtils = componentsUtils;
49         this.propertyOperation = propertyOperation;
50     }
51
52     @Override
53     public Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, String propertiesOwnerId, List<ComponentInstancePropInput> propsToDeclare) {
54         log.debug("#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}", component.getUniqueId(), propertiesOwnerId);
55         return resolvePropertiesOwner(component, propertiesOwnerId)
56                 .map(propertyOwner -> declarePropertiesAsInputs(component, propertyOwner, propsToDeclare))
57                 .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
58     }
59
60     abstract PROPERTYTYPE createDeclaredProperty(PropertyDataDefinition prop);
61
62     abstract Either<?, StorageOperationStatus> updatePropertiesValues(Component component, String propertiesOwnerId, List<PROPERTYTYPE> properties);
63
64     abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(Component component, String propertiesOwnerId);
65
66     abstract void addPropertiesListToInput(PROPERTYTYPE declaredProp, PropertyDataDefinition originalProp, InputDefinition input);
67
68     private StorageOperationStatus onPropertiesOwnerNotFound(String componentId, String propertiesOwnerId) {
69         log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId, componentId);
70         return StorageOperationStatus.NOT_FOUND;
71     }
72
73     private Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
74         PropertiesDeclarationData inputsProperties = createInputsAndOverridePropertiesValues(component.getUniqueId(), propertiesOwner, propsToDeclare);
75         return updatePropertiesValues(component, propertiesOwner.getUniqueId(), inputsProperties.getPropertiesToUpdate())
76                 .left()
77                 .map(updatePropsRes -> inputsProperties.getInputsToCreate());
78     }
79
80     private PropertiesDeclarationData createInputsAndOverridePropertiesValues(String componentId, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
81         List<PROPERTYTYPE> declaredProperties = new ArrayList<>();
82         List<InputDefinition> createdInputs = propsToDeclare.stream()
83                 .map(propInput -> declarePropertyInput(componentId, propertiesOwner, declaredProperties, propInput))
84                 .collect(Collectors.toList());
85         return new PropertiesDeclarationData(createdInputs, declaredProperties);
86     }
87
88     private InputDefinition declarePropertyInput(String componentId, PropertiesOwner propertiesOwner, List<PROPERTYTYPE> declaredProperties, ComponentInstancePropInput propInput) {
89         PropertyDataDefinition prop = resolveProperty(declaredProperties, propInput);
90         propInput.setOwnerId(null);
91         propInput.setParentUniqueId(null);
92         InputDefinition inputDefinition = createInput(componentId, propertiesOwner, propInput, prop);
93         PROPERTYTYPE declaredProperty = createDeclaredProperty(prop);
94         if(!declaredProperties.contains(declaredProperty)){
95             declaredProperties.add(declaredProperty);
96         }
97         addPropertiesListToInput(declaredProperty, prop, inputDefinition);
98         return inputDefinition;
99     }
100
101     private InputDefinition createInput(String componentId, PropertiesOwner propertiesOwner, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
102         String generatedInputName = generateInputName(propertiesOwner.getNormalizedName(), propInput);
103         return createInputFromProperty(componentId, propertiesOwner, generatedInputName, propInput, prop);
104     }
105
106     private String generateInputName(String inputName, ComponentInstancePropInput propInput) {
107         String[] parsedPropNames = propInput.getParsedPropNames();
108         if(parsedPropNames != null){
109             for(String str: parsedPropNames){
110                 inputName += "_"  + str;
111             }
112         } else {
113             inputName += "_"  + propInput.getName();
114         }
115         return inputName;
116     }
117
118     private PropertyDataDefinition resolveProperty(List<PROPERTYTYPE> propertiesToCreate, ComponentInstancePropInput propInput) {
119         Optional<PROPERTYTYPE> resolvedProperty = propertiesToCreate.stream()
120                 .filter(p -> p.getName().equals(propInput.getName()))
121                 .findFirst();
122         return resolvedProperty.isPresent() ? resolvedProperty.get() : propInput;
123     }
124
125     private InputDefinition createInputFromProperty(String componentId, PropertiesOwner propertiesOwner, String inputName, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
126         String propertiesName = propInput.getPropertiesName() ;
127         PropertyDefinition selectedProp = propInput.getInput();
128         String[] parsedPropNames = propInput.getParsedPropNames();
129         InputDefinition input;
130         boolean complexProperty = false;
131         if(propertiesName != null && !propertiesName.isEmpty() && selectedProp != null){
132             complexProperty = true;
133             input = new InputDefinition(selectedProp);
134         }else{
135             input = new InputDefinition(prop);
136         }
137         input.setDefaultValue(prop.getValue());
138         input.setName(inputName);
139         input.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, input.getName()));
140         input.setInputPath(propertiesName);
141         input.setInstanceUniqueId(propertiesOwner.getUniqueId());
142         input.setPropertyId(propInput.getUniqueId());
143         changePropertyValueToGetInputValue(inputName, parsedPropNames, input, prop, complexProperty);
144         ((IComponentInstanceConnectedElement)prop).setComponentInstanceId(propertiesOwner.getUniqueId());
145         ((IComponentInstanceConnectedElement)prop).setComponentInstanceName(propertiesOwner.getName());
146         return input;
147     }
148
149     private void changePropertyValueToGetInputValue(String inputName, String[] parsedPropNames, InputDefinition input, PropertyDataDefinition prop, boolean complexProperty) {
150         JSONObject jobject = new JSONObject();
151         if(prop.getValue() == null || prop.getValue().isEmpty()){
152             if(complexProperty){
153
154                 jobject = createJSONValueForProperty(parsedPropNames.length -1, parsedPropNames, jobject, inputName);
155                 prop.setValue(jobject.toJSONString());
156
157             }else{
158
159                 jobject.put(GET_INPUT, input.getName());
160                 prop.setValue(jobject.toJSONString());
161
162             }
163
164         }else{
165
166             String value = prop.getValue();
167             Object objValue =  new Yaml().load(value);
168             if( objValue instanceof Map || objValue  instanceof List){
169                 if(!complexProperty){
170                     jobject.put(GET_INPUT, input.getName());
171                     prop.setValue(jobject.toJSONString());
172
173
174                 }else{
175                     Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
176                     createInputValue(mappedToscaTemplate, 1, parsedPropNames, inputName);
177
178                     String json = gson.toJson(mappedToscaTemplate);
179                     prop.setValue(json);
180
181                 }
182
183             }else{
184                 jobject.put(GET_INPUT, input.getName());
185                 prop.setValue(jobject.toJSONString());
186
187             }
188
189         }
190
191
192         if(CollectionUtils.isEmpty(prop.getGetInputValues())){
193             prop.setGetInputValues(new ArrayList<>());
194         }
195         List<GetInputValueDataDefinition> getInputValues = prop.getGetInputValues();
196
197         GetInputValueDataDefinition getInputValueDataDefinition = new GetInputValueDataDefinition();
198         getInputValueDataDefinition.setInputId(input.getUniqueId());
199         getInputValueDataDefinition.setInputName(input.getName());
200         getInputValues.add(getInputValueDataDefinition);
201     }
202
203     private  JSONObject createJSONValueForProperty (int i, String [] parsedPropNames, JSONObject ooj, String inputName){
204
205         while(i >= 1){
206             if( i == parsedPropNames.length -1){
207                 JSONObject jobProp = new JSONObject();
208                 jobProp.put(GET_INPUT, inputName);
209                 ooj.put(parsedPropNames[i], jobProp);
210                 i--;
211                 return createJSONValueForProperty (i, parsedPropNames, ooj, inputName);
212             }else{
213                 JSONObject res = new JSONObject();
214                 res.put(parsedPropNames[i], ooj);
215                 i --;
216                 res =  createJSONValueForProperty (i, parsedPropNames, res, inputName);
217                 return res;
218             }
219         }
220
221         return ooj;
222     }
223
224     private  Map<String, Object> createInputValue(Map<String, Object> lhm1, int index, String[] inputNames, String inputName){
225         while(index < inputNames.length){
226             if(lhm1.containsKey(inputNames[index])){
227                 Object value = lhm1.get(inputNames[index]);
228                 if (value instanceof Map){
229                     if(index == inputNames.length -1){
230                         ((Map) value).put(GET_INPUT, inputName);
231                         return (Map) value;
232
233                     }else{
234                         index++;
235                         return  createInputValue((Map)value, index, inputNames, inputName);
236                     }
237                 }else{
238                     Map<String, Object> jobProp = new HashMap<>();
239                     if(index == inputNames.length -1){
240                         jobProp.put(GET_INPUT, inputName);
241                         lhm1.put(inputNames[index], jobProp);
242                         return lhm1;
243                     }else{
244                         lhm1.put(inputNames[index], jobProp);
245                         index++;
246                         return  createInputValue(jobProp, index, inputNames, inputName);
247                     }
248                 }
249             }else{
250                 Map<String, Object> jobProp = new HashMap<>();
251                 lhm1.put(inputNames[index], jobProp);
252                 if(index == inputNames.length -1){
253                     jobProp.put(GET_INPUT, inputName);
254                     return jobProp;
255                 }else{
256                     index++;
257                     return  createInputValue(jobProp, index, inputNames, inputName);
258                 }
259             }
260         }
261         return lhm1;
262     }
263
264     private class PropertiesDeclarationData {
265         private List<InputDefinition> inputsToCreate;
266         private List<PROPERTYTYPE> propertiesToUpdate;
267
268         PropertiesDeclarationData(List<InputDefinition> inputsToCreate, List<PROPERTYTYPE> propertiesToUpdate) {
269             this.inputsToCreate = inputsToCreate;
270             this.propertiesToUpdate = propertiesToUpdate;
271         }
272
273         List<InputDefinition> getInputsToCreate() {
274             return inputsToCreate;
275         }
276
277         List<PROPERTYTYPE> getPropertiesToUpdate() {
278             return propertiesToUpdate;
279         }
280     }
281
282     Either<InputDefinition, ResponseFormat>  prepareValueBeforeDelete(InputDefinition inputForDelete, PropertyDataDefinition inputValue, List<String> pathOfComponentInstances) {
283         Either<InputDefinition, ResponseFormat> deleteEither = Either.left(inputForDelete);
284         String value = inputValue.getValue();
285         Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
286
287         resetInputName(mappedToscaTemplate, inputForDelete.getName());
288
289         value = "";
290         if(!mappedToscaTemplate.isEmpty()){
291             Either result = cleanNestedMap(mappedToscaTemplate , true);
292             Map modifiedMappedToscaTemplate = mappedToscaTemplate;
293             if (result.isLeft())
294                 modifiedMappedToscaTemplate = (Map)result.left().value();
295             else
296                 log.warn("Map cleanup failed -> " +result.right().value().toString());    //continue, don't break operation
297             value = gson.toJson(modifiedMappedToscaTemplate);
298         }
299         inputValue.setValue(value);
300
301
302         List<GetInputValueDataDefinition> getInputsValues = inputValue.getGetInputValues();
303         if(getInputsValues != null && !getInputsValues.isEmpty()){
304             Optional<GetInputValueDataDefinition> op = getInputsValues.stream().filter(gi -> gi.getInputId().equals(inputForDelete.getUniqueId())).findAny();
305             if(op.isPresent()){
306                 getInputsValues.remove(op.get());
307             }
308         }
309         inputValue.setGetInputValues(getInputsValues);
310
311         Either<String, TitanOperationStatus> findDefaultValue = propertyOperation.findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(), inputValue.getDefaultValue());
312         if (findDefaultValue.isRight()) {
313             deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(DaoStatusConverter.convertTitanStatusToStorageStatus(findDefaultValue.right().value()))));
314             return deleteEither;
315
316         }
317         String defaultValue = findDefaultValue.left().value();
318         inputValue.setDefaultValue(defaultValue);
319         log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
320         return deleteEither;
321     }
322
323     private void resetInputName(Map<String, Object> lhm1, String inputName){
324         for (Map.Entry<String, Object> entry : lhm1.entrySet()) {
325             String key = entry.getKey();
326             Object value = entry.getValue();
327             if (value instanceof String && ((String) value).equalsIgnoreCase(inputName) && key.equals(GET_INPUT)) {
328                 value = "";
329                 lhm1.remove(key);
330             } else if (value instanceof Map) {
331                 Map<String, Object> subMap = (Map<String, Object>)value;
332                 resetInputName(subMap, inputName);
333             } else {
334                 continue;
335             }
336
337         }
338     }
339
340     private Either cleanNestedMap( Map mappedToscaTemplate , boolean deepClone  ){
341         if (MapUtils.isNotEmpty( mappedToscaTemplate ) ){
342             if (deepClone){
343                 if (!(mappedToscaTemplate instanceof HashMap))
344                     return Either.right("expecting mappedToscaTemplate as HashMap ,recieved "+ mappedToscaTemplate.getClass().getSimpleName() );
345                 else
346                     mappedToscaTemplate = (HashMap)((HashMap) mappedToscaTemplate).clone();
347             }
348             return Either.left( (Map) cleanEmptyNestedValuesInMap( mappedToscaTemplate , LOOP_PROTECTION_LEVEL ) );
349         }
350         else {
351             log.debug("mappedToscaTemplate is empty ");
352             return Either.right("mappedToscaTemplate is empty ");
353         }
354     }
355
356     /*        Mutates the object
357      *        Tail recurse -> traverse the tosca elements and remove nested empty map properties
358      *        this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
359      *
360      *        @param  toscaElement - expected map of tosca values
361      *        @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
362      **/
363     private Object cleanEmptyNestedValuesInMap(Object toscaElement , short loopProtectionLevel ){
364         //region - Stop if map is empty
365         if (loopProtectionLevel<=0 || toscaElement==null || !(toscaElement instanceof  Map))
366             return toscaElement;
367         //endregion
368         //region - Remove empty map entries & return null iff empty map
369         if ( MapUtils.isNotEmpty( (Map)toscaElement ) ) {
370             Object ret;
371             Set<Object> keysToRemove = new HashSet<>();                                                                 // use different set to avoid ConcurrentModificationException
372             for( Object key : ((Map)toscaElement).keySet() ) {
373                 Object value = ((Map) toscaElement).get(key);
374                 ret = cleanEmptyNestedValuesInMap(value , --loopProtectionLevel );
375                 if ( ret == null )
376                     keysToRemove.add(key);
377             }
378             Collection set = ((Map) toscaElement).keySet();
379             if (CollectionUtils.isNotEmpty(set))
380                 set.removeAll(keysToRemove);
381
382             if ( isEmptyNestedMap(toscaElement) )                                                                         // similar to < if ( MapUtils.isEmpty( (Map)toscaElement ) ) > ,but adds nested map check
383                 return null;
384         }
385         //endregion
386         else
387             return null;
388         return toscaElement;
389     }
390
391     //@returns true iff map nested maps are all empty
392     //ignores other collection objects
393     private boolean isEmptyNestedMap(Object element){
394         boolean isEmpty = true;
395         if (element != null){
396             if ( element instanceof Map ){
397                 if (MapUtils.isEmpty((Map)element))
398                     isEmpty = true;
399                 else
400                 {
401                     for( Object key : ((Map)(element)).keySet() ){
402                         Object value =  ((Map)(element)).get(key);
403                         isEmpty &= isEmptyNestedMap( value );
404                     }
405                 }
406             } else {
407                 isEmpty = false;
408             }
409         }
410         return isEmpty;
411     }
412
413 }