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=========================================================
21 package org.openecomp.sdc.be.components.attribute;
23 import static org.openecomp.sdc.common.api.Constants.GET_ATTRIBUTE;
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;
34 import java.util.Objects;
35 import java.util.Optional;
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;
56 public abstract class DefaultAttributeDeclarator<PROPERTYOWNER extends PropertiesOwner, ATTRIBUTETYPE extends AttributeDataDefinition>
57 implements AttributeDeclarator {
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();
64 protected DefaultAttributeDeclarator() {
68 public Either<List<OutputDefinition>, StorageOperationStatus> declareAttributesAsOutputs(final Component component,
69 final String propertiesOwnerId,
70 final List<ComponentInstanceAttribOutput> attribsToDeclare) {
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)));
79 protected abstract ATTRIBUTETYPE createDeclaredAttribute(final AttributeDataDefinition attributeDataDefinition);
81 protected abstract Either<?, StorageOperationStatus> updateAttributesValues(final Component component,
82 final String propertiesOwnerId,
83 final List<ATTRIBUTETYPE> attributetypeList);
85 protected abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(final Component component, final String propertiesOwnerId);
87 private StorageOperationStatus onPropertiesOwnerNotFound(final String componentId, final String propertiesOwnerId) {
88 log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId,
90 return StorageOperationStatus.NOT_FOUND;
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())
101 .map(updatePropsRes -> attributesDeclarationData.getOutputsToCreate());
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);
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);
124 return outputDefinition;
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;
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);
143 private String generateOutputName(final String outputName, final ComponentInstanceAttribOutput attribOutput) {
144 final String declaredInputName;
145 final String[] parsedPropNames = attribOutput.getParsedAttribNames();
147 if (parsedPropNames != null) {
148 declaredInputName = handleInputName(outputName, parsedPropNames);
150 final String[] propName = {attribOutput.getName()};
151 declaredInputName = handleInputName(outputName, propName);
154 return declaredInputName;
157 private String handleInputName(final String outputName, final String[] parsedPropNames) {
158 final StringBuilder prefix = new StringBuilder();
161 if (Objects.isNull(outputName)) {
162 prefix.append(parsedPropNames[0]);
165 prefix.append(outputName);
169 while (startingIndex < parsedPropNames.length) {
170 prefix.append(UNDERSCORE);
171 prefix.append(parsedPropNames[startingIndex]);
175 return prefix.toString();
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()))
183 return resolvedAttribute.isPresent() ? resolvedAttribute.get() : attribOutput;
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());
201 outputDefinition = new OutputDefinition(attribute);
202 outputDefinition.setDefaultValue(attribute.getValue());
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));
211 if (attribute instanceof IComponentInstanceConnectedElement) {
212 ((IComponentInstanceConnectedElement) attribute).setComponentInstanceId(propertiesOwner.getUniqueId());
213 ((IComponentInstanceConnectedElement) attribute).setComponentInstanceName(propertiesOwner.getName());
215 changeOutputValueToGetAttributeValue(outputName, parsedAttribNames, outputDefinition, attribute, complexProperty);
216 return outputDefinition;
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());
232 GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
233 output.setValue(jsonObject.toJSONString());
236 final Object objValue = new Yaml().load(value);
237 if (objValue instanceof Map || objValue instanceof List) {
238 if (!complexProperty) {
240 GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
241 output.setValue(jsonObject.toJSONString());
243 final Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
244 createOutputValue(mappedToscaTemplate, 1, parsedPropNames, outputName);
246 output.setValue(gson.toJson(mappedToscaTemplate));
251 GET_ATTRIBUTE, Arrays.asList(output.getAttributes().get(0).getComponentInstanceName(), attributeDataDefinition.getName()));
252 output.setValue(jsonObject.toJSONString());
256 if (CollectionUtils.isEmpty(attributeDataDefinition.getGetOutputValues())) {
257 attributeDataDefinition.setGetOutputValues(new ArrayList<>());
259 final List<GetOutputValueDataDefinition> getOutputValues = attributeDataDefinition.getGetOutputValues();
261 final GetOutputValueDataDefinition getOutputValueDataDefinition = new GetOutputValueDataDefinition();
262 getOutputValueDataDefinition.setOutputId(output.getUniqueId());
263 getOutputValueDataDefinition.setOutputName(output.getName());
264 getOutputValues.add(getOutputValueDataDefinition);
267 private JSONObject createJSONValueForProperty(int i, final String[] parsedPropNames, final JSONObject ooj, final String outputName) {
270 if (i == parsedPropNames.length - 1) {
271 final JSONObject jobProp = new JSONObject();
272 jobProp.put(GET_ATTRIBUTE, outputName);
273 ooj.put(parsedPropNames[i], jobProp);
275 return createJSONValueForProperty(i, parsedPropNames, ooj, outputName);
277 final JSONObject res = new JSONObject();
278 res.put(parsedPropNames[i], ooj);
280 return createJSONValueForProperty(i, parsedPropNames, res, outputName);
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);
295 return createOutputValue((Map) value, ++index, outputNames, outputName);
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);
304 lhm1.put(outputNames[index], jobProp);
305 return createOutputValue(jobProp, ++index, outputNames, outputName);
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);
315 return createOutputValue(jobProp, ++index, outputNames, outputName);
322 private class AttributesDeclarationData {
324 private final List<OutputDefinition> outputsToCreate;
325 private final List<ATTRIBUTETYPE> attributesToUpdate;
327 AttributesDeclarationData(final List<OutputDefinition> outputsToCreate,
328 final List<ATTRIBUTETYPE> attributesToUpdate) {
329 this.outputsToCreate = outputsToCreate;
330 this.attributesToUpdate = attributesToUpdate;
333 List<OutputDefinition> getOutputsToCreate() {
334 return outputsToCreate;
337 List<ATTRIBUTETYPE> getAttributesToUpdate() {
338 return attributesToUpdate;
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);
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;
357 // String defaultValue = findDefaultValue.left().value();
358 // inputValue.set_default(defaultValue);
359 // log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
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);
369 resetOutputName(mappedToscaTemplate, outputForDelete.getName());
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();
378 log.warn("Map cleanup failed -> {}", result.right().value()); //continue, don't break operation
380 value = gson.toJson(modifiedMappedToscaTemplate);
382 outputValue.setValue(value);
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);
390 outputValue.setGetOutputValues(getInputsValues);
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)) {
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)) {
409 private Either cleanNestedMap(Map mappedToscaTemplate, final boolean deepClone) {
410 if (MapUtils.isNotEmpty(mappedToscaTemplate)) {
412 if (!(mappedToscaTemplate instanceof HashMap)) {
414 "expecting mappedToscaTemplate as HashMap ,recieved " + mappedToscaTemplate.getClass()
417 mappedToscaTemplate = (HashMap) ((HashMap) mappedToscaTemplate).clone();
420 return Either.left((Map) cleanEmptyNestedValuesInMap(mappedToscaTemplate, LOOP_PROTECTION_LEVEL));
422 log.debug("mappedToscaTemplate is empty ");
423 return Either.right("mappedToscaTemplate is empty ");
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
431 * @param toscaElement - expected map of tosca values
432 * @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
434 private Object cleanEmptyNestedValuesInMap(final Object toscaElement, short loopProtectionLevel) {
435 if (loopProtectionLevel <= 0 || toscaElement == null || !(toscaElement instanceof Map)) {
438 if (MapUtils.isNotEmpty((Map) toscaElement)) {
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);
445 keysToRemove.add(key);
448 final Collection set = ((Map) toscaElement).keySet();
449 if (CollectionUtils.isNotEmpty(set)) {
450 set.removeAll(keysToRemove);
453 if (isEmptyNestedMap(toscaElement)) {
464 * @return true if map nested maps are all empty, ignores other collection objects
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);