e53a36250865ce9cd779ff31292eda181e97d37d
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / attribute / DefaultAttributeDeclarator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2021, Nordix Foundation. 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
21 package org.openecomp.sdc.be.components.attribute;
22
23 import static org.openecomp.sdc.common.api.Constants.GET_ATTRIBUTE;
24
25 import com.google.gson.Gson;
26 import fj.data.Either;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.stream.Collectors;
38 import org.apache.commons.collections4.CollectionUtils;
39 import org.apache.commons.collections4.MapUtils;
40 import org.apache.commons.lang3.StringUtils;
41 import org.json.simple.JSONObject;
42 import org.openecomp.sdc.be.datatypes.elements.AttributeDataDefinition;
43 import org.openecomp.sdc.be.datatypes.elements.GetOutputValueDataDefinition;
44 import org.openecomp.sdc.be.datatypes.elements.PropertiesOwner;
45 import org.openecomp.sdc.be.model.AttributeDefinition;
46 import org.openecomp.sdc.be.model.Component;
47 import org.openecomp.sdc.be.model.ComponentInstanceAttribOutput;
48 import org.openecomp.sdc.be.model.IComponentInstanceConnectedElement;
49 import org.openecomp.sdc.be.model.OutputDefinition;
50 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
51 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
52 import org.openecomp.sdc.common.log.wrappers.Logger;
53 import org.openecomp.sdc.exception.ResponseFormat;
54 import org.yaml.snakeyaml.Yaml;
55
56 public abstract class DefaultAttributeDeclarator<PROPERTYOWNER extends PropertiesOwner, ATTRIBUTETYPE extends AttributeDataDefinition>
57     implements AttributeDeclarator {
58
59     private static final Logger log = Logger.getLogger(DefaultAttributeDeclarator.class);
60     private static final short LOOP_PROTECTION_LEVEL = 10;
61     private static final String UNDERSCORE = "_";
62     private final Gson gson = new Gson();
63
64     protected DefaultAttributeDeclarator() {
65     }
66
67     @Override
68     public Either<List<OutputDefinition>, StorageOperationStatus> declareAttributesAsOutputs(final Component component,
69                                                                                              final String propertiesOwnerId,
70                                                                                              final List<ComponentInstanceAttribOutput> attribsToDeclare) {
71         log.debug(
72             "#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}",
73             component.getUniqueId(), propertiesOwnerId);
74         return resolvePropertiesOwner(component, propertiesOwnerId)
75             .map(propertyOwner -> declareAttributesAsOutputs(component, propertyOwner, attribsToDeclare))
76             .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
77     }
78
79     protected abstract ATTRIBUTETYPE createDeclaredAttribute(final AttributeDataDefinition attributeDataDefinition);
80
81     protected abstract Either<?, StorageOperationStatus> updateAttributesValues(final Component component,
82                                                                                 final String propertiesOwnerId,
83                                                                                 final List<ATTRIBUTETYPE> attributetypeList);
84
85     protected abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(final Component component, final String propertiesOwnerId);
86
87     private StorageOperationStatus onPropertiesOwnerNotFound(final String componentId, final String propertiesOwnerId) {
88         log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId,
89             componentId);
90         return StorageOperationStatus.NOT_FOUND;
91     }
92
93     private Either<List<OutputDefinition>, StorageOperationStatus> declareAttributesAsOutputs(final Component component,
94                                                                                               final PROPERTYOWNER propertiesOwner,
95                                                                                               final List<ComponentInstanceAttribOutput> attributesToDeclare) {
96         final AttributesDeclarationData attributesDeclarationData
97             = createOutputsAndOverrideAttributesValues(component, propertiesOwner, attributesToDeclare);
98         return updateAttributesValues(component, propertiesOwner.getUniqueId(),
99             attributesDeclarationData.getAttributesToUpdate())
100             .left()
101             .map(updatePropsRes -> attributesDeclarationData.getOutputsToCreate());
102     }
103
104     private AttributesDeclarationData createOutputsAndOverrideAttributesValues(final Component component,
105                                                                                final PROPERTYOWNER propertiesOwner,
106                                                                                final List<ComponentInstanceAttribOutput> attributesToDeclare) {
107         final List<ATTRIBUTETYPE> declaredAttributes = new ArrayList<>();
108         final List<OutputDefinition> createdInputs = attributesToDeclare.stream()
109             .map(attributeOutput -> declareAttributeOutput(component, propertiesOwner, declaredAttributes, attributeOutput))
110             .collect(Collectors.toList());
111         return new AttributesDeclarationData(createdInputs, declaredAttributes);
112     }
113
114     private OutputDefinition declareAttributeOutput(final Component component,
115                                                     final PROPERTYOWNER propertiesOwner,
116                                                     final List<ATTRIBUTETYPE> declaredAttributes,
117                                                     final ComponentInstanceAttribOutput attribOutput) {
118         final AttributeDataDefinition attribute = resolveAttribute(declaredAttributes, attribOutput);
119         final OutputDefinition outputDefinition = createOutput(component, propertiesOwner, attribOutput, attribute);
120         final ATTRIBUTETYPE declaredAttribute = createDeclaredAttribute(attribute);
121         if (!declaredAttributes.contains(declaredAttribute)) {
122             declaredAttributes.add(declaredAttribute);
123         }
124         return outputDefinition;
125     }
126
127     private OutputDefinition createOutput(final Component component,
128                                           final PROPERTYOWNER propertiesOwner,
129                                           final ComponentInstanceAttribOutput attribOutput,
130                                           final AttributeDataDefinition attributeDataDefinition) {
131         String generatedInputPrefix = propertiesOwner.getNormalizedName();
132         if (propertiesOwner.getUniqueId().equals(attribOutput.getParentUniqueId())) {
133             //Creating input from property create on self using add property..Do not add the prefix
134             generatedInputPrefix = null;
135         }
136
137         final String generatedOutputName = generateOutputName(generatedInputPrefix, attribOutput);
138         log.debug("createInput: propOwner.uniqueId={}, attribOutput.parentUniqueId={}",
139             propertiesOwner.getUniqueId(), attribOutput.getParentUniqueId());
140         return createOutputFromAttribute(component.getUniqueId(), propertiesOwner, generatedOutputName, attribOutput, attributeDataDefinition);
141     }
142
143     private String generateOutputName(final String outputName, final ComponentInstanceAttribOutput attribOutput) {
144         final String declaredInputName;
145         final String[] parsedPropNames = attribOutput.getParsedAttribNames();
146
147         if (parsedPropNames != null) {
148             declaredInputName = handleInputName(outputName, parsedPropNames);
149         } else {
150             final String[] propName = {attribOutput.getName()};
151             declaredInputName = handleInputName(outputName, propName);
152         }
153
154         return declaredInputName;
155     }
156
157     private String handleInputName(final String outputName, final String[] parsedPropNames) {
158         final StringBuilder prefix = new StringBuilder();
159         int startingIndex;
160
161         if (Objects.isNull(outputName)) {
162             prefix.append(parsedPropNames[0]);
163             startingIndex = 1;
164         } else {
165             prefix.append(outputName);
166             startingIndex = 0;
167         }
168
169         while (startingIndex < parsedPropNames.length) {
170             prefix.append(UNDERSCORE);
171             prefix.append(parsedPropNames[startingIndex]);
172             startingIndex++;
173         }
174
175         return prefix.toString();
176     }
177
178     private AttributeDataDefinition resolveAttribute(final List<ATTRIBUTETYPE> attributesToCreate,
179                                                      final ComponentInstanceAttribOutput attribOutput) {
180         final Optional<ATTRIBUTETYPE> resolvedAttribute = attributesToCreate.stream()
181             .filter(p -> p.getName().equals(attribOutput.getName()))
182             .findFirst();
183         return resolvedAttribute.isPresent() ? resolvedAttribute.get() : attribOutput;
184     }
185
186     OutputDefinition createOutputFromAttribute(final String componentId,
187                                                final PROPERTYOWNER propertiesOwner,
188                                                final String outputName,
189                                                final ComponentInstanceAttribOutput attributeOutput,
190                                                final AttributeDataDefinition attribute) {
191         final String attributesName = attributeOutput.getAttributesName();
192         final AttributeDefinition selectedAttrib = attributeOutput.getOutput();
193         final String[] parsedAttribNames = attributeOutput.getParsedAttribNames();
194         OutputDefinition outputDefinition;
195         boolean complexProperty = false;
196         if (attributesName != null && !attributesName.isEmpty() && selectedAttrib != null) {
197             complexProperty = true;
198             outputDefinition = new OutputDefinition(selectedAttrib);
199             outputDefinition.setDefaultValue(selectedAttrib.getValue());
200         } else {
201             outputDefinition = new OutputDefinition(attribute);
202             outputDefinition.setDefaultValue(attribute.getValue());
203         }
204         outputDefinition.setName(outputName);
205         outputDefinition.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, outputDefinition.getName()));
206         outputDefinition.setOutputPath(attributesName);
207         outputDefinition.setInstanceUniqueId(propertiesOwner.getUniqueId());
208         outputDefinition.setAttributeId(attributeOutput.getUniqueId());
209         outputDefinition.setAttributes(Arrays.asList(attributeOutput));
210
211         if (attribute instanceof IComponentInstanceConnectedElement) {
212             ((IComponentInstanceConnectedElement) attribute).setComponentInstanceId(propertiesOwner.getUniqueId());
213             ((IComponentInstanceConnectedElement) attribute).setComponentInstanceName(propertiesOwner.getName());
214         }
215         changeOutputValueToGetAttributeValue(outputName, parsedAttribNames, outputDefinition, attribute, complexProperty);
216         return outputDefinition;
217     }
218
219     private void changeOutputValueToGetAttributeValue(final String outputName,
220                                                       final String[] parsedPropNames,
221                                                       final OutputDefinition output,
222                                                       final AttributeDataDefinition attributeDataDefinition,
223                                                       final boolean complexProperty) {
224         JSONObject jsonObject = new JSONObject();
225         final String value = attributeDataDefinition.getValue();
226         if (StringUtils.isEmpty(value)) {
227             if (complexProperty) {
228                 jsonObject = createJSONValueForProperty(parsedPropNames.length - 1, parsedPropNames, jsonObject, outputName);
229                 attributeDataDefinition.setValue(jsonObject.toJSONString());
230             } else {
231                 jsonObject.put(
232                     GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
233                 output.setValue(jsonObject.toJSONString());
234             }
235         } else {
236             final Object objValue = new Yaml().load(value);
237             if (objValue instanceof Map || objValue instanceof List) {
238                 if (!complexProperty) {
239                     jsonObject.put(
240                         GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
241                     output.setValue(jsonObject.toJSONString());
242                 } else {
243                     final Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
244                     createOutputValue(mappedToscaTemplate, 1, parsedPropNames, outputName);
245
246                     output.setValue(gson.toJson(mappedToscaTemplate));
247                 }
248
249             } else {
250                 jsonObject.put(
251                     GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
252                 output.setValue(jsonObject.toJSONString());
253             }
254         }
255
256         if (CollectionUtils.isEmpty(attributeDataDefinition.getGetOutputValues())) {
257             attributeDataDefinition.setGetOutputValues(new ArrayList<>());
258         }
259         final List<GetOutputValueDataDefinition> getOutputValues = attributeDataDefinition.getGetOutputValues();
260
261         final GetOutputValueDataDefinition getOutputValueDataDefinition = new GetOutputValueDataDefinition();
262         getOutputValueDataDefinition.setOutputId(output.getUniqueId());
263         getOutputValueDataDefinition.setOutputName(output.getName());
264         getOutputValues.add(getOutputValueDataDefinition);
265     }
266
267     private JSONObject createJSONValueForProperty(int i, final String[] parsedPropNames, final JSONObject ooj, final String outputName) {
268
269         while (i >= 1) {
270             if (i == parsedPropNames.length - 1) {
271                 final JSONObject jobProp = new JSONObject();
272                 jobProp.put(GET_ATTRIBUTE, outputName);
273                 ooj.put(parsedPropNames[i], jobProp);
274                 i--;
275                 return createJSONValueForProperty(i, parsedPropNames, ooj, outputName);
276             } else {
277                 final JSONObject res = new JSONObject();
278                 res.put(parsedPropNames[i], ooj);
279                 i--;
280                 return createJSONValueForProperty(i, parsedPropNames, res, outputName);
281             }
282         }
283
284         return ooj;
285     }
286
287     private Map<String, Object> createOutputValue(final Map<String, Object> lhm1, int index, final String[] outputNames, final String outputName) {
288         while (index < outputNames.length) {
289             if (lhm1.containsKey(outputNames[index])) {
290                 final Object value = lhm1.get(outputNames[index]);
291                 if (value instanceof Map) {
292                     if (index == outputNames.length - 1) {
293                         return (Map<String, Object>) ((Map) value).put(GET_ATTRIBUTE, outputName);
294                     } else {
295                         return createOutputValue((Map) value, ++index, outputNames, outputName);
296                     }
297                 } else {
298                     final Map<String, Object> jobProp = new HashMap<>();
299                     if (index == outputNames.length - 1) {
300                         jobProp.put(GET_ATTRIBUTE, outputName);
301                         lhm1.put(outputNames[index], jobProp);
302                         return lhm1;
303                     } else {
304                         lhm1.put(outputNames[index], jobProp);
305                         return createOutputValue(jobProp, ++index, outputNames, outputName);
306                     }
307                 }
308             } else {
309                 final Map<String, Object> jobProp = new HashMap<>();
310                 lhm1.put(outputNames[index], jobProp);
311                 if (index == outputNames.length - 1) {
312                     jobProp.put(GET_ATTRIBUTE, outputName);
313                     return jobProp;
314                 } else {
315                     return createOutputValue(jobProp, ++index, outputNames, outputName);
316                 }
317             }
318         }
319         return lhm1;
320     }
321
322     private class AttributesDeclarationData {
323
324         private final List<OutputDefinition> outputsToCreate;
325         private final List<ATTRIBUTETYPE> attributesToUpdate;
326
327         AttributesDeclarationData(final List<OutputDefinition> outputsToCreate,
328                                   final List<ATTRIBUTETYPE> attributesToUpdate) {
329             this.outputsToCreate = outputsToCreate;
330             this.attributesToUpdate = attributesToUpdate;
331         }
332
333         List<OutputDefinition> getOutputsToCreate() {
334             return outputsToCreate;
335         }
336
337         List<ATTRIBUTETYPE> getAttributesToUpdate() {
338             return attributesToUpdate;
339         }
340     }
341
342     Either<OutputDefinition, ResponseFormat> prepareValueBeforeDelete(final OutputDefinition inputForDelete,
343                                                                       final AttributeDataDefinition inputValue,
344                                                                       final List<String> pathOfComponentInstances) {
345         final Either<OutputDefinition, ResponseFormat> deleteEither = prepareValueBeforeDelete(inputForDelete, inputValue);
346
347 //        Either<String, JanusGraphOperationStatus> findDefaultValue = propertyOperation
348 //            .findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(),
349 //                (String) inputValue.get_default());
350 //        if (findDefaultValue.isRight()) {
351 //            deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils
352 //                .convertFromStorageResponse(
353 //                    DaoStatusConverter.convertJanusGraphStatusToStorageStatus(findDefaultValue.right().value()))));
354 //            return deleteEither;
355 //
356 //        }
357 //        String defaultValue = findDefaultValue.left().value();
358 //        inputValue.set_default(defaultValue);
359 //        log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
360         return deleteEither;
361     }
362
363     private Either<OutputDefinition, ResponseFormat> prepareValueBeforeDelete(final OutputDefinition outputForDelete,
364                                                                               final AttributeDataDefinition outputValue) {
365         final Either<OutputDefinition, ResponseFormat> deleteEither = Either.left(outputForDelete);
366         String value = outputValue.getValue();
367         final Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
368
369         resetOutputName(mappedToscaTemplate, outputForDelete.getName());
370
371         value = "";
372         if (MapUtils.isNotEmpty(mappedToscaTemplate)) {
373             final Either result = cleanNestedMap(mappedToscaTemplate, true);
374             Map modifiedMappedToscaTemplate = mappedToscaTemplate;
375             if (result.isLeft()) {
376                 modifiedMappedToscaTemplate = (Map) result.left().value();
377             } else {
378                 log.warn("Map cleanup failed -> {}", result.right().value());    //continue, don't break operation
379             }
380             value = gson.toJson(modifiedMappedToscaTemplate);
381         }
382         outputValue.setValue(value);
383
384         final List<GetOutputValueDataDefinition> getInputsValues = outputValue.getGetOutputValues();
385         if (getInputsValues != null && !getInputsValues.isEmpty()) {
386             final Optional<GetOutputValueDataDefinition> op =
387                 getInputsValues.stream().filter(gi -> gi.getOutputId().equals(outputForDelete.getUniqueId())).findAny();
388             op.ifPresent(getInputsValues::remove);
389         }
390         outputValue.setGetOutputValues(getInputsValues);
391         return deleteEither;
392     }
393
394     private void resetOutputName(final Map<String, Object> lhm1, final String outputName) {
395         for (final Map.Entry<String, Object> entry : lhm1.entrySet()) {
396             final String key = entry.getKey();
397             final Object value = entry.getValue();
398             if (value instanceof String && ((String) value).equalsIgnoreCase(outputName) && GET_ATTRIBUTE.equals(key)) {
399                 lhm1.remove(key);
400             } else if (value instanceof Map) {
401                 final Map<String, Object> subMap = (Map<String, Object>) value;
402                 resetOutputName(subMap, outputName);
403             } else if (value instanceof List && ((List) value).contains(outputName) && GET_ATTRIBUTE.equals(key)) {
404                 lhm1.remove(key);
405             }
406         }
407     }
408
409     private Either cleanNestedMap(Map mappedToscaTemplate, final boolean deepClone) {
410         if (MapUtils.isNotEmpty(mappedToscaTemplate)) {
411             if (deepClone) {
412                 if (!(mappedToscaTemplate instanceof HashMap)) {
413                     return Either.right(
414                         "expecting mappedToscaTemplate as HashMap ,recieved " + mappedToscaTemplate.getClass()
415                             .getSimpleName());
416                 } else {
417                     mappedToscaTemplate = (HashMap) ((HashMap) mappedToscaTemplate).clone();
418                 }
419             }
420             return Either.left((Map) cleanEmptyNestedValuesInMap(mappedToscaTemplate, LOOP_PROTECTION_LEVEL));
421         } else {
422             log.debug("mappedToscaTemplate is empty ");
423             return Either.right("mappedToscaTemplate is empty ");
424         }
425     }
426
427     /*        Mutates the object
428      *        Tail recurse -> traverse the tosca elements and remove nested empty map properties
429      *        this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
430      *
431      *        @param  toscaElement - expected map of tosca values
432      *        @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
433      **/
434     private Object cleanEmptyNestedValuesInMap(final Object toscaElement, short loopProtectionLevel) {
435         if (loopProtectionLevel <= 0 || toscaElement == null || !(toscaElement instanceof Map)) {
436             return toscaElement;
437         }
438         if (MapUtils.isNotEmpty((Map) toscaElement)) {
439             Object ret;
440             final Set<Object> keysToRemove = new HashSet<>();                                                                 // use different set to avoid ConcurrentModificationException
441             for (final Object key : ((Map) toscaElement).keySet()) {
442                 final Object value = ((Map) toscaElement).get(key);
443                 ret = cleanEmptyNestedValuesInMap(value, --loopProtectionLevel);
444                 if (ret == null) {
445                     keysToRemove.add(key);
446                 }
447             }
448             final Collection set = ((Map) toscaElement).keySet();
449             if (CollectionUtils.isNotEmpty(set)) {
450                 set.removeAll(keysToRemove);
451             }
452
453             if (isEmptyNestedMap(toscaElement)) {
454                 return null;
455             }
456         } else {
457             return null;
458         }
459         return toscaElement;
460     }
461
462     /**
463      * @param element
464      * @return true if map nested maps are all empty, ignores other collection objects
465      */
466     private boolean isEmptyNestedMap(final Object element) {
467         boolean isEmpty = true;
468         if (element != null) {
469             if (element instanceof Map) {
470                 if (MapUtils.isNotEmpty((Map) element)) {
471                     for (final Object key : ((Map) (element)).keySet()) {
472                         Object value = ((Map) (element)).get(key);
473                         isEmpty &= isEmptyNestedMap(value);
474                     }
475                 }
476             } else {
477                 isEmpty = false;
478             }
479         }
480         return isEmpty;
481     }
482
483 }