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