Provide user to specify the ouput name while declaring the atrributes
[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 package org.openecomp.sdc.be.components.attribute;
21
22 import static org.openecomp.sdc.common.api.Constants.GET_ATTRIBUTE;
23
24 import com.google.gson.Gson;
25 import fj.data.Either;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 import org.apache.commons.collections4.CollectionUtils;
37 import org.apache.commons.collections4.MapUtils;
38 import org.apache.commons.lang3.StringUtils;
39 import org.json.simple.JSONObject;
40 import org.openecomp.sdc.be.datatypes.elements.AttributeDataDefinition;
41 import org.openecomp.sdc.be.datatypes.elements.GetOutputValueDataDefinition;
42 import org.openecomp.sdc.be.datatypes.elements.PropertiesOwner;
43 import org.openecomp.sdc.be.model.AttributeDefinition;
44 import org.openecomp.sdc.be.model.Component;
45 import org.openecomp.sdc.be.model.ComponentInstanceAttribOutput;
46 import org.openecomp.sdc.be.model.IComponentInstanceConnectedElement;
47 import org.openecomp.sdc.be.model.OutputDefinition;
48 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
49 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
50 import org.openecomp.sdc.common.log.wrappers.Logger;
51 import org.yaml.snakeyaml.Yaml;
52
53 public abstract class DefaultAttributeDeclarator<PROPERTYOWNER extends PropertiesOwner, ATTRIBUTETYPE extends AttributeDataDefinition> implements
54     AttributeDeclarator {
55
56     private static final Logger log = Logger.getLogger(DefaultAttributeDeclarator.class);
57     private static final String UNDERSCORE = "_";
58     private final Gson gson = new Gson();
59
60     protected DefaultAttributeDeclarator() {
61     }
62
63     @Override
64     public Either<List<OutputDefinition>, StorageOperationStatus> declareAttributesAsOutputs(final Component component,
65                                                                                              final String propertiesOwnerId,
66                                                                                              final List<ComponentInstanceAttribOutput> attribsToDeclare) {
67         log.debug("#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}", component.getUniqueId(),
68             propertiesOwnerId);
69         return resolvePropertiesOwner(component, propertiesOwnerId)
70             .map(propertyOwner -> declareAttributesAsOutputs(component, propertyOwner, attribsToDeclare))
71             .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
72     }
73
74     protected abstract ATTRIBUTETYPE createDeclaredAttribute(final AttributeDataDefinition attributeDataDefinition);
75
76     protected abstract Either<?, StorageOperationStatus> updateAttributesValues(final Component component, final String propertiesOwnerId,
77                                                                                 final List<ATTRIBUTETYPE> attributetypeList);
78
79     protected abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(final Component component, final String propertiesOwnerId);
80
81     private StorageOperationStatus onPropertiesOwnerNotFound(final String componentId, final String propertiesOwnerId) {
82         log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId, componentId);
83         return StorageOperationStatus.NOT_FOUND;
84     }
85
86     private Either<List<OutputDefinition>, StorageOperationStatus> declareAttributesAsOutputs(final Component component,
87                                                                                               final PROPERTYOWNER propertiesOwner,
88                                                                                               final List<ComponentInstanceAttribOutput> attributesToDeclare) {
89         final AttributesDeclarationData attributesDeclarationData = createOutputsAndOverrideAttributesValues(component, propertiesOwner,
90             attributesToDeclare);
91         return updateAttributesValues(component, propertiesOwner.getUniqueId(), attributesDeclarationData.getAttributesToUpdate()).left()
92             .map(updatePropsRes -> attributesDeclarationData.getOutputsToCreate());
93     }
94
95     private AttributesDeclarationData createOutputsAndOverrideAttributesValues(final Component component, final PROPERTYOWNER propertiesOwner,
96                                                                                final List<ComponentInstanceAttribOutput> attributesToDeclare) {
97         final List<ATTRIBUTETYPE> declaredAttributes = new ArrayList<>();
98         final List<OutputDefinition> createdInputs = attributesToDeclare.stream()
99             .map(attributeOutput -> declareAttributeOutput(component, propertiesOwner, declaredAttributes, attributeOutput))
100             .collect(Collectors.toList());
101         return new AttributesDeclarationData(createdInputs, declaredAttributes);
102     }
103
104     private OutputDefinition declareAttributeOutput(final Component component, final PROPERTYOWNER propertiesOwner,
105                                                     final List<ATTRIBUTETYPE> declaredAttributes, final ComponentInstanceAttribOutput attribOutput) {
106         final AttributeDataDefinition attribute = resolveAttribute(declaredAttributes, attribOutput);
107         final OutputDefinition outputDefinition = createOutput(component, propertiesOwner, attribOutput, attribute);
108         final ATTRIBUTETYPE declaredAttribute = createDeclaredAttribute(attribute);
109         if (!declaredAttributes.contains(declaredAttribute)) {
110             declaredAttributes.add(declaredAttribute);
111         }
112         return outputDefinition;
113     }
114
115     private OutputDefinition createOutput(final Component component, final PROPERTYOWNER propertiesOwner,
116                                           final ComponentInstanceAttribOutput attribOutput, final AttributeDataDefinition attributeDataDefinition) {
117         String generatedInputPrefix = propertiesOwner.getNormalizedName();
118         if (propertiesOwner.getUniqueId().equals(attribOutput.getParentUniqueId())) {
119             //Creating input from property create on self using add property..Do not add the prefix
120             generatedInputPrefix = null;
121         }
122         String generatedOutputName = null;
123         if (StringUtils.isNotEmpty(attribOutput.getOutputName())) {
124             generatedOutputName = attribOutput.getOutputName();
125         } else {
126             generatedOutputName = generateOutputName(generatedInputPrefix, attribOutput);
127         }
128
129         log.debug("createInput: propOwner.uniqueId={}, attribOutput.parentUniqueId={}", propertiesOwner.getUniqueId(),
130             attribOutput.getParentUniqueId());
131         return createOutputFromAttribute(component.getUniqueId(), propertiesOwner, generatedOutputName, attribOutput, attributeDataDefinition);
132     }
133
134     private String generateOutputName(final String outputName, final ComponentInstanceAttribOutput attribOutput) {
135         final String declaredInputName;
136         final String[] parsedPropNames = attribOutput.getParsedAttribNames();
137         if (parsedPropNames != null) {
138             declaredInputName = handleInputName(outputName, parsedPropNames);
139         } else {
140             final String[] propName = {attribOutput.getName()};
141             declaredInputName = handleInputName(outputName, propName);
142         }
143         return declaredInputName;
144     }
145
146     private String handleInputName(final String outputName, final String[] parsedPropNames) {
147         final StringBuilder prefix = new StringBuilder();
148         int startingIndex;
149         if (Objects.isNull(outputName)) {
150             prefix.append(parsedPropNames[0]);
151             startingIndex = 1;
152         } else {
153             prefix.append(outputName);
154             startingIndex = 0;
155         }
156         while (startingIndex < parsedPropNames.length) {
157             prefix.append(UNDERSCORE);
158             prefix.append(parsedPropNames[startingIndex]);
159             startingIndex++;
160         }
161         return prefix.toString();
162     }
163
164     private AttributeDataDefinition resolveAttribute(final List<ATTRIBUTETYPE> attributesToCreate, final ComponentInstanceAttribOutput attribOutput) {
165         final Optional<ATTRIBUTETYPE> resolvedAttribute = attributesToCreate.stream().filter(p -> p.getName().equals(attribOutput.getName()))
166             .findFirst();
167         return resolvedAttribute.isPresent() ? resolvedAttribute.get() : attribOutput;
168     }
169
170     OutputDefinition createOutputFromAttribute(final String componentId, final PROPERTYOWNER propertiesOwner, final String outputName,
171                                                final ComponentInstanceAttribOutput attributeOutput, final AttributeDataDefinition attribute) {
172         final String attributesName = attributeOutput.getAttributesName();
173         final AttributeDefinition selectedAttrib = attributeOutput.getOutput();
174         final String[] parsedAttribNames = attributeOutput.getParsedAttribNames();
175         OutputDefinition outputDefinition;
176         boolean complexProperty = false;
177         if (attributesName != null && !attributesName.isEmpty() && selectedAttrib != null) {
178             complexProperty = true;
179             outputDefinition = new OutputDefinition(selectedAttrib);
180             outputDefinition.setDefaultValue(selectedAttrib.getValue());
181         } else {
182             outputDefinition = new OutputDefinition(attribute);
183             outputDefinition.setDefaultValue(attribute.getValue());
184         }
185         outputDefinition.setName(outputName);
186         outputDefinition.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, outputDefinition.getName()));
187         outputDefinition.setOutputPath(attributesName);
188         outputDefinition.setInstanceUniqueId(propertiesOwner.getUniqueId());
189         outputDefinition.setAttributeId(attributeOutput.getUniqueId());
190         outputDefinition.setAttribute(attributeOutput);
191         if (attribute instanceof IComponentInstanceConnectedElement) {
192             ((IComponentInstanceConnectedElement) attribute).setComponentInstanceId(propertiesOwner.getUniqueId());
193             ((IComponentInstanceConnectedElement) attribute).setComponentInstanceName(propertiesOwner.getName());
194         }
195         changeOutputValueToGetAttributeValue(outputName, parsedAttribNames, outputDefinition, attribute, complexProperty);
196         return outputDefinition;
197     }
198
199     private void changeOutputValueToGetAttributeValue(final String outputName, final String[] parsedPropNames, final OutputDefinition output,
200                                                       final AttributeDataDefinition attributeDataDefinition, final boolean complexProperty) {
201         JSONObject jsonObject = new JSONObject();
202         final String value = attributeDataDefinition.getValue();
203         if (StringUtils.isEmpty(value)) {
204             if (complexProperty) {
205                 jsonObject = createJSONValueForProperty(parsedPropNames.length - 1, parsedPropNames, jsonObject, outputName);
206                 attributeDataDefinition.setValue(jsonObject.toJSONString());
207             } else {
208                 jsonObject
209                     .put(GET_ATTRIBUTE, Arrays.asList(output.getAttribute().getComponentInstanceName(), attributeDataDefinition.getName()));
210                 output.setValue(jsonObject.toJSONString());
211             }
212         } else {
213             final Object objValue = new Yaml().load(value);
214             if (objValue instanceof Map || objValue instanceof List) {
215                 if (!complexProperty) {
216                     jsonObject.put(GET_ATTRIBUTE,
217                         Arrays.asList(output.getAttribute().getComponentInstanceName(), attributeDataDefinition.getName()));
218                     output.setValue(jsonObject.toJSONString());
219                 } else {
220                     final Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
221                     createOutputValue(mappedToscaTemplate, 1, parsedPropNames, outputName);
222                     output.setValue(gson.toJson(mappedToscaTemplate));
223                 }
224             } else {
225                 jsonObject
226                     .put(GET_ATTRIBUTE, Arrays.asList(output.getAttribute().getComponentInstanceName(), attributeDataDefinition.getName()));
227                 output.setValue(jsonObject.toJSONString());
228             }
229         }
230         if (CollectionUtils.isEmpty(attributeDataDefinition.getGetOutputValues())) {
231             attributeDataDefinition.setGetOutputValues(new ArrayList<>());
232         }
233         final List<GetOutputValueDataDefinition> getOutputValues = attributeDataDefinition.getGetOutputValues();
234         final GetOutputValueDataDefinition getOutputValueDataDefinition = new GetOutputValueDataDefinition();
235         getOutputValueDataDefinition.setOutputId(output.getUniqueId());
236         getOutputValueDataDefinition.setOutputName(output.getName());
237         getOutputValues.add(getOutputValueDataDefinition);
238     }
239
240     private JSONObject createJSONValueForProperty(int i, final String[] parsedPropNames, final JSONObject ooj, final String outputName) {
241         while (i >= 1) {
242             if (i == parsedPropNames.length - 1) {
243                 final JSONObject jobProp = new JSONObject();
244                 jobProp.put(GET_ATTRIBUTE, outputName);
245                 ooj.put(parsedPropNames[i], jobProp);
246                 i--;
247                 return createJSONValueForProperty(i, parsedPropNames, ooj, outputName);
248             } else {
249                 final JSONObject res = new JSONObject();
250                 res.put(parsedPropNames[i], ooj);
251                 i--;
252                 return createJSONValueForProperty(i, parsedPropNames, res, outputName);
253             }
254         }
255         return ooj;
256     }
257
258     private Map<String, Object> createOutputValue(final Map<String, Object> lhm1, int index, final String[] outputNames, final String outputName) {
259         while (index < outputNames.length) {
260             if (lhm1.containsKey(outputNames[index])) {
261                 final Object value = lhm1.get(outputNames[index]);
262                 if (value instanceof Map) {
263                     if (index == outputNames.length - 1) {
264                         return (Map<String, Object>) ((Map) value).put(GET_ATTRIBUTE, outputName);
265                     } else {
266                         return createOutputValue((Map) value, ++index, outputNames, outputName);
267                     }
268                 } else {
269                     final Map<String, Object> jobProp = new HashMap<>();
270                     if (index == outputNames.length - 1) {
271                         jobProp.put(GET_ATTRIBUTE, outputName);
272                         lhm1.put(outputNames[index], jobProp);
273                         return lhm1;
274                     } else {
275                         lhm1.put(outputNames[index], jobProp);
276                         return createOutputValue(jobProp, ++index, outputNames, outputName);
277                     }
278                 }
279             } else {
280                 final Map<String, Object> jobProp = new HashMap<>();
281                 lhm1.put(outputNames[index], jobProp);
282                 if (index == outputNames.length - 1) {
283                     jobProp.put(GET_ATTRIBUTE, outputName);
284                     return jobProp;
285                 } else {
286                     return createOutputValue(jobProp, ++index, outputNames, outputName);
287                 }
288             }
289         }
290         return lhm1;
291     }
292
293     private void resetOutputName(final Map<String, Object> lhm1, final String outputName) {
294         for (final Map.Entry<String, Object> entry : lhm1.entrySet()) {
295             final String key = entry.getKey();
296             final Object value = entry.getValue();
297             if (value instanceof String && ((String) value).equalsIgnoreCase(outputName) && GET_ATTRIBUTE.equals(key)) {
298                 lhm1.remove(key);
299             } else if (value instanceof Map) {
300                 final Map<String, Object> subMap = (Map<String, Object>) value;
301                 resetOutputName(subMap, outputName);
302             } else if (value instanceof List && ((List) value).contains(outputName) && GET_ATTRIBUTE.equals(key)) {
303                 lhm1.remove(key);
304             }
305         }
306     }
307
308     /*        Mutates the object
309      *        Tail recurse -> traverse the tosca elements and remove nested empty map properties
310      *        this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
311      *
312      *        @param  toscaElement - expected map of tosca values
313      *        @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
314      **/
315     private Object cleanEmptyNestedValuesInMap(final Object toscaElement, short loopProtectionLevel) {
316         if (loopProtectionLevel <= 0 || !(toscaElement instanceof Map)) {
317             return toscaElement;
318         }
319         Map<Object, Object> toscaMap = (Map<Object, Object>) toscaElement;
320         if (MapUtils.isNotEmpty(toscaMap)) {
321             Object ret;
322             final Set<Object> keysToRemove = new HashSet<>();                                                                 // use different set to avoid ConcurrentModificationException
323             for (final Object key : toscaMap.keySet()) {
324                 final Object value = toscaMap.get(key);
325                 ret = cleanEmptyNestedValuesInMap(value, --loopProtectionLevel);
326                 if (ret == null) {
327                     keysToRemove.add(key);
328                 }
329             }
330             final Set<Object> keySet = toscaMap.keySet();
331             if (CollectionUtils.isNotEmpty(keySet)) {
332                 keySet.removeAll(keysToRemove);
333             }
334             if (isEmptyNestedMap(toscaElement)) {
335                 return null;
336             }
337         } else {
338             return null;
339         }
340         return toscaElement;
341     }
342
343     /**
344      * @param element
345      * @return true if map nested maps are all empty, ignores other collection objects
346      */
347     private boolean isEmptyNestedMap(final Object element) {
348         boolean isEmpty = true;
349         if (element != null) {
350             if (element instanceof Map) {
351                 if (MapUtils.isNotEmpty((Map) element)) {
352                     for (final Object key : ((Map) (element)).keySet()) {
353                         Object value = ((Map) (element)).get(key);
354                         isEmpty &= isEmptyNestedMap(value);
355                     }
356                 }
357             } else {
358                 isEmpty = false;
359             }
360         }
361         return isEmpty;
362     }
363
364     private class AttributesDeclarationData {
365
366         private final List<OutputDefinition> outputsToCreate;
367         private final List<ATTRIBUTETYPE> attributesToUpdate;
368
369         AttributesDeclarationData(final List<OutputDefinition> outputsToCreate, final List<ATTRIBUTETYPE> attributesToUpdate) {
370             this.outputsToCreate = outputsToCreate;
371             this.attributesToUpdate = attributesToUpdate;
372         }
373
374         List<OutputDefinition> getOutputsToCreate() {
375             return outputsToCreate;
376         }
377
378         List<ATTRIBUTETYPE> getAttributesToUpdate() {
379             return attributesToUpdate;
380         }
381     }
382 }