re base code
[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         propInput.setOwnerId(null);
79         propInput.setParentUniqueId(null);
80         InputDefinition inputDefinition = createInput(componentId, propertiesOwner, propInput, prop);
81         PROPERTYTYPE declaredProperty = createDeclaredProperty(prop);
82         if(!declaredProperties.contains(declaredProperty)){
83             declaredProperties.add(declaredProperty);
84         }
85         addPropertiesListToInput(declaredProperty, inputDefinition);
86         return inputDefinition;
87     }
88
89     private InputDefinition createInput(String componentId, PROPERTYOWNER propertiesOwner, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
90         String generatedInputName = generateInputName(propertiesOwner.getNormalizedName(), propInput);
91         return createInputFromProperty(componentId, propertiesOwner, generatedInputName, propInput, prop);
92     }
93
94     private String generateInputName(String inputName, ComponentInstancePropInput propInput) {
95         String[] parsedPropNames = propInput.getParsedPropNames();
96         if(parsedPropNames != null){
97             for(String str: parsedPropNames){
98                 inputName += "_"  + str;
99             }
100         } else {
101             inputName += "_"  + propInput.getName();
102         }
103         return inputName;
104     }
105
106     private PropertyDataDefinition resolveProperty(List<PROPERTYTYPE> propertiesToCreate, ComponentInstancePropInput propInput) {
107         Optional<PROPERTYTYPE> resolvedProperty = propertiesToCreate.stream()
108                 .filter(p -> p.getName().equals(propInput.getName()))
109                 .findFirst();
110         return resolvedProperty.isPresent() ? resolvedProperty.get() : propInput;
111     }
112
113     InputDefinition createInputFromProperty(String componentId, PROPERTYOWNER propertiesOwner, String inputName, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
114         String propertiesName = propInput.getPropertiesName() ;
115         PropertyDefinition selectedProp = propInput.getInput();
116         String[] parsedPropNames = propInput.getParsedPropNames();
117         InputDefinition input;
118         boolean complexProperty = false;
119         if(propertiesName != null && !propertiesName.isEmpty() && selectedProp != null){
120             complexProperty = true;
121             input = new InputDefinition(selectedProp);
122             input.setDefaultValue(selectedProp.getValue());
123         }else{
124             input = new InputDefinition(prop);
125             input.setDefaultValue(prop.getValue());
126         }
127         input.setName(inputName);
128         input.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, input.getName()));
129         input.setInputPath(propertiesName);
130         input.setInstanceUniqueId(propertiesOwner.getUniqueId());
131         input.setPropertyId(propInput.getUniqueId());
132         input.setValue(null);
133         changePropertyValueToGetInputValue(inputName, parsedPropNames, input, prop, complexProperty);
134         ((IComponentInstanceConnectedElement)prop).setComponentInstanceId(propertiesOwner.getUniqueId());
135         ((IComponentInstanceConnectedElement)prop).setComponentInstanceName(propertiesOwner.getName());
136         return input;
137     }
138
139     private void changePropertyValueToGetInputValue(String inputName, String[] parsedPropNames, InputDefinition input, PropertyDataDefinition prop, boolean complexProperty) {
140         JSONObject jobject = new JSONObject();
141         if(prop.getValue() == null || prop.getValue().isEmpty()){
142             if(complexProperty){
143
144                 jobject = createJSONValueForProperty(parsedPropNames.length -1, parsedPropNames, jobject, inputName);
145                 prop.setValue(jobject.toJSONString());
146
147             }else{
148
149                 jobject.put(GET_INPUT, input.getName());
150                 prop.setValue(jobject.toJSONString());
151
152             }
153
154         }else{
155
156             String value = prop.getValue();
157             Object objValue =  new Yaml().load(value);
158             if( objValue instanceof Map || objValue  instanceof List){
159                 if(!complexProperty){
160                     jobject.put(GET_INPUT, input.getName());
161                     prop.setValue(jobject.toJSONString());
162
163
164                 }else{
165                     Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
166                     createInputValue(mappedToscaTemplate, 1, parsedPropNames, inputName);
167
168                     String json = gson.toJson(mappedToscaTemplate);
169                     prop.setValue(json);
170
171                 }
172
173             }else{
174                 jobject.put(GET_INPUT, input.getName());
175                 prop.setValue(jobject.toJSONString());
176
177             }
178
179         }
180
181
182         if(CollectionUtils.isEmpty(prop.getGetInputValues())){
183             prop.setGetInputValues(new ArrayList<>());
184         }
185         List<GetInputValueDataDefinition> getInputValues = prop.getGetInputValues();
186
187         GetInputValueDataDefinition getInputValueDataDefinition = new GetInputValueDataDefinition();
188         getInputValueDataDefinition.setInputId(input.getUniqueId());
189         getInputValueDataDefinition.setInputName(input.getName());
190         getInputValues.add(getInputValueDataDefinition);
191     }
192
193     private  JSONObject createJSONValueForProperty (int i, String [] parsedPropNames, JSONObject ooj, String inputName){
194
195         while(i >= 1){
196             if( i == parsedPropNames.length -1){
197                 JSONObject jobProp = new JSONObject();
198                 jobProp.put(GET_INPUT, inputName);
199                 ooj.put(parsedPropNames[i], jobProp);
200                 i--;
201                 return createJSONValueForProperty (i, parsedPropNames, ooj, inputName);
202             }else{
203                 JSONObject res = new JSONObject();
204                 res.put(parsedPropNames[i], ooj);
205                 i --;
206                 res =  createJSONValueForProperty (i, parsedPropNames, res, inputName);
207                 return res;
208             }
209         }
210
211         return ooj;
212     }
213
214     private  Map<String, Object> createInputValue(Map<String, Object> lhm1, int index, String[] inputNames, String inputName){
215         while(index < inputNames.length){
216             if(lhm1.containsKey(inputNames[index])){
217                 Object value = lhm1.get(inputNames[index]);
218                 if (value instanceof Map){
219                     if(index == inputNames.length -1){
220                         ((Map) value).put(GET_INPUT, inputName);
221                         return (Map) value;
222
223                     }else{
224                         index++;
225                         return  createInputValue((Map)value, index, inputNames, inputName);
226                     }
227                 }else{
228                     Map<String, Object> jobProp = new HashMap<>();
229                     if(index == inputNames.length -1){
230                         jobProp.put(GET_INPUT, inputName);
231                         lhm1.put(inputNames[index], jobProp);
232                         return lhm1;
233                     }else{
234                         lhm1.put(inputNames[index], jobProp);
235                         index++;
236                         return  createInputValue(jobProp, index, inputNames, inputName);
237                     }
238                 }
239             }else{
240                 Map<String, Object> jobProp = new HashMap<>();
241                 lhm1.put(inputNames[index], jobProp);
242                 if(index == inputNames.length -1){
243                     jobProp.put(GET_INPUT, inputName);
244                     return jobProp;
245                 }else{
246                     index++;
247                     return  createInputValue(jobProp, index, inputNames, inputName);
248                 }
249             }
250         }
251         return lhm1;
252     }
253
254     private class PropertiesDeclarationData {
255         private List<InputDefinition> inputsToCreate;
256         private List<PROPERTYTYPE> propertiesToUpdate;
257
258         PropertiesDeclarationData(List<InputDefinition> inputsToCreate, List<PROPERTYTYPE> propertiesToUpdate) {
259             this.inputsToCreate = inputsToCreate;
260             this.propertiesToUpdate = propertiesToUpdate;
261         }
262
263         List<InputDefinition> getInputsToCreate() {
264             return inputsToCreate;
265         }
266
267         List<PROPERTYTYPE> getPropertiesToUpdate() {
268             return propertiesToUpdate;
269         }
270     }
271
272     Either<InputDefinition, ResponseFormat>  prepareValueBeforeDelete(InputDefinition inputForDelete, PropertyDataDefinition inputValue, List<String> pathOfComponentInstances) {
273         Either<InputDefinition, ResponseFormat> deleteEither = Either.left(inputForDelete);
274         String value = inputValue.getValue();
275         Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
276
277         resetInputName(mappedToscaTemplate, inputForDelete.getName());
278
279         value = "";
280         if(!mappedToscaTemplate.isEmpty()){
281             Either result = cleanNestedMap(mappedToscaTemplate , true);
282             Map modifiedMappedToscaTemplate = mappedToscaTemplate;
283             if (result.isLeft())
284                 modifiedMappedToscaTemplate = (Map)result.left().value();
285             else
286                 log.warn("Map cleanup failed -> " +result.right().value().toString());    //continue, don't break operation
287             value = gson.toJson(modifiedMappedToscaTemplate);
288         }
289         inputValue.setValue(value);
290
291
292         List<GetInputValueDataDefinition> getInputsValues = inputValue.getGetInputValues();
293         if(getInputsValues != null && !getInputsValues.isEmpty()){
294             Optional<GetInputValueDataDefinition> op = getInputsValues.stream().filter(gi -> gi.getInputId().equals(inputForDelete.getUniqueId())).findAny();
295             if(op.isPresent()){
296                 getInputsValues.remove(op.get());
297             }
298         }
299         inputValue.setGetInputValues(getInputsValues);
300
301         Either<String, TitanOperationStatus> findDefaultValue = propertyOperation.findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(), inputValue.getDefaultValue());
302         if (findDefaultValue.isRight()) {
303             deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(DaoStatusConverter.convertTitanStatusToStorageStatus(findDefaultValue.right().value()))));
304             return deleteEither;
305
306         }
307         String defaultValue = findDefaultValue.left().value();
308         inputValue.setDefaultValue(defaultValue);
309         log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
310         return deleteEither;
311     }
312
313     private void resetInputName(Map<String, Object> lhm1, String inputName){
314         for (Map.Entry<String, Object> entry : lhm1.entrySet()) {
315             String key = entry.getKey();
316             Object value = entry.getValue();
317             if (value instanceof String && ((String) value).equalsIgnoreCase(inputName) && key.equals(GET_INPUT)) {
318                 value = "";
319                 lhm1.remove(key);
320             } else if (value instanceof Map) {
321                 Map<String, Object> subMap = (Map<String, Object>)value;
322                 resetInputName(subMap, inputName);
323             } else {
324                 continue;
325             }
326
327         }
328     }
329
330     private Either cleanNestedMap( Map mappedToscaTemplate , boolean deepClone  ){
331         if (MapUtils.isNotEmpty( mappedToscaTemplate ) ){
332             if (deepClone){
333                 if (!(mappedToscaTemplate instanceof HashMap))
334                     return Either.right("expecting mappedToscaTemplate as HashMap ,recieved "+ mappedToscaTemplate.getClass().getSimpleName() );
335                 else
336                     mappedToscaTemplate = (HashMap)((HashMap) mappedToscaTemplate).clone();
337             }
338             return Either.left( (Map) cleanEmptyNestedValuesInMap( mappedToscaTemplate , LOOP_PROTECTION_LEVEL ) );
339         }
340         else {
341             log.debug("mappedToscaTemplate is empty ");
342             return Either.right("mappedToscaTemplate is empty ");
343         }
344     }
345
346     /*        Mutates the object
347      *        Tail recurse -> traverse the tosca elements and remove nested empty map properties
348      *        this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
349      *
350      *        @param  toscaElement - expected map of tosca values
351      *        @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
352      **/
353     private Object cleanEmptyNestedValuesInMap(Object toscaElement , short loopProtectionLevel ){
354         if (loopProtectionLevel<=0 || toscaElement==null || !(toscaElement instanceof  Map))
355             return toscaElement;
356         if ( MapUtils.isNotEmpty( (Map)toscaElement ) ) {
357             Object ret;
358             Set<Object> keysToRemove = new HashSet<>();                                                                 // use different set to avoid ConcurrentModificationException
359             for( Object key : ((Map)toscaElement).keySet() ) {
360                 Object value = ((Map) toscaElement).get(key);
361                 ret = cleanEmptyNestedValuesInMap(value , --loopProtectionLevel );
362                 if ( ret == null )
363                     keysToRemove.add(key);
364             }
365             Collection set = ((Map) toscaElement).keySet();
366             if (CollectionUtils.isNotEmpty(set))
367                 set.removeAll(keysToRemove);
368
369             if ( isEmptyNestedMap(toscaElement) )
370                 return null;
371         }
372         else
373             return null;
374         return toscaElement;
375     }
376
377     //@returns true iff map nested maps are all empty
378     //ignores other collection objects
379     private boolean isEmptyNestedMap(Object element){
380         boolean isEmpty = true;
381         if (element != null){
382             if ( element instanceof Map ){
383                 if (MapUtils.isEmpty((Map)element))
384                     isEmpty = true;
385                 else
386                 {
387                     for( Object key : ((Map)(element)).keySet() ){
388                         Object value =  ((Map)(element)).get(key);
389                         isEmpty &= isEmptyNestedMap( value );
390                     }
391                 }
392             } else {
393                 isEmpty = false;
394             }
395         }
396         return isEmpty;
397     }
398
399 }