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