2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
20 package org.openecomp.sdc.be.components.attribute;
22 import static org.openecomp.sdc.common.api.Constants.GET_ATTRIBUTE;
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;
32 import java.util.Objects;
33 import java.util.Optional;
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;
53 public abstract class DefaultAttributeDeclarator<PROPERTYOWNER extends PropertiesOwner, ATTRIBUTETYPE extends AttributeDataDefinition> implements
56 private static final Logger log = Logger.getLogger(DefaultAttributeDeclarator.class);
57 private static final String UNDERSCORE = "_";
58 private final Gson gson = new Gson();
60 protected DefaultAttributeDeclarator() {
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(),
69 return resolvePropertiesOwner(component, propertiesOwnerId)
70 .map(propertyOwner -> declareAttributesAsOutputs(component, propertyOwner, attribsToDeclare))
71 .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
74 protected abstract ATTRIBUTETYPE createDeclaredAttribute(final AttributeDataDefinition attributeDataDefinition);
76 protected abstract Either<?, StorageOperationStatus> updateAttributesValues(final Component component, final String propertiesOwnerId,
77 final List<ATTRIBUTETYPE> attributetypeList);
79 protected abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(final Component component, final String propertiesOwnerId);
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;
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,
91 return updateAttributesValues(component, propertiesOwner.getUniqueId(), attributesDeclarationData.getAttributesToUpdate()).left()
92 .map(updatePropsRes -> attributesDeclarationData.getOutputsToCreate());
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);
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);
112 return outputDefinition;
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;
122 String generatedOutputName = null;
123 if (StringUtils.isNotEmpty(attribOutput.getOutputName())) {
124 generatedOutputName = attribOutput.getOutputName();
126 generatedOutputName = generateOutputName(generatedInputPrefix, attribOutput);
129 log.debug("createInput: propOwner.uniqueId={}, attribOutput.parentUniqueId={}", propertiesOwner.getUniqueId(),
130 attribOutput.getParentUniqueId());
131 return createOutputFromAttribute(component.getUniqueId(), propertiesOwner, generatedOutputName, attribOutput, attributeDataDefinition);
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);
140 final String[] propName = {attribOutput.getName()};
141 declaredInputName = handleInputName(outputName, propName);
143 return declaredInputName;
146 private String handleInputName(final String outputName, final String[] parsedPropNames) {
147 final StringBuilder prefix = new StringBuilder();
149 if (Objects.isNull(outputName)) {
150 prefix.append(parsedPropNames[0]);
153 prefix.append(outputName);
156 while (startingIndex < parsedPropNames.length) {
157 prefix.append(UNDERSCORE);
158 prefix.append(parsedPropNames[startingIndex]);
161 return prefix.toString();
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()))
167 return resolvedAttribute.isPresent() ? resolvedAttribute.get() : attribOutput;
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());
182 outputDefinition = new OutputDefinition(attribute);
183 outputDefinition.setDefaultValue(attribute.getValue());
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());
195 changeOutputValueToGetAttributeValue(outputName, parsedAttribNames, outputDefinition, attribute, complexProperty);
196 return outputDefinition;
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());
209 .put(GET_ATTRIBUTE, Arrays.asList(output.getAttribute().getComponentInstanceName(), attributeDataDefinition.getName()));
210 output.setValue(jsonObject.toJSONString());
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());
220 final Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
221 createOutputValue(mappedToscaTemplate, 1, parsedPropNames, outputName);
222 output.setValue(gson.toJson(mappedToscaTemplate));
226 .put(GET_ATTRIBUTE, Arrays.asList(output.getAttribute().getComponentInstanceName(), attributeDataDefinition.getName()));
227 output.setValue(jsonObject.toJSONString());
230 if (CollectionUtils.isEmpty(attributeDataDefinition.getGetOutputValues())) {
231 attributeDataDefinition.setGetOutputValues(new ArrayList<>());
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);
240 private JSONObject createJSONValueForProperty(int i, final String[] parsedPropNames, final JSONObject ooj, final String outputName) {
242 if (i == parsedPropNames.length - 1) {
243 final JSONObject jobProp = new JSONObject();
244 jobProp.put(GET_ATTRIBUTE, outputName);
245 ooj.put(parsedPropNames[i], jobProp);
247 return createJSONValueForProperty(i, parsedPropNames, ooj, outputName);
249 final JSONObject res = new JSONObject();
250 res.put(parsedPropNames[i], ooj);
252 return createJSONValueForProperty(i, parsedPropNames, res, outputName);
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);
266 return createOutputValue((Map) value, ++index, outputNames, outputName);
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);
275 lhm1.put(outputNames[index], jobProp);
276 return createOutputValue(jobProp, ++index, outputNames, outputName);
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);
286 return createOutputValue(jobProp, ++index, outputNames, outputName);
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)) {
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)) {
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
312 * @param toscaElement - expected map of tosca values
313 * @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
315 private Object cleanEmptyNestedValuesInMap(final Object toscaElement, short loopProtectionLevel) {
316 if (loopProtectionLevel <= 0 || !(toscaElement instanceof Map)) {
319 Map<Object, Object> toscaMap = (Map<Object, Object>) toscaElement;
320 if (MapUtils.isNotEmpty(toscaMap)) {
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);
327 keysToRemove.add(key);
330 final Set<Object> keySet = toscaMap.keySet();
331 if (CollectionUtils.isNotEmpty(keySet)) {
332 keySet.removeAll(keysToRemove);
334 if (isEmptyNestedMap(toscaElement)) {
345 * @return true if map nested maps are all empty, ignores other collection objects
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);
364 private class AttributesDeclarationData {
366 private final List<OutputDefinition> outputsToCreate;
367 private final List<ATTRIBUTETYPE> attributesToUpdate;
369 AttributesDeclarationData(final List<OutputDefinition> outputsToCreate, final List<ATTRIBUTETYPE> attributesToUpdate) {
370 this.outputsToCreate = outputsToCreate;
371 this.attributesToUpdate = attributesToUpdate;
374 List<OutputDefinition> getOutputsToCreate() {
375 return outputsToCreate;
378 List<ATTRIBUTETYPE> getAttributesToUpdate() {
379 return attributesToUpdate;