1 package org.openecomp.sdc.be.components.property;
3 import static org.openecomp.sdc.common.api.Constants.GET_INPUT;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.HashSet;
11 import java.util.Optional;
13 import java.util.stream.Collectors;
15 import org.apache.commons.collections.CollectionUtils;
16 import org.apache.commons.collections.MapUtils;
17 import org.json.simple.JSONObject;
18 import org.openecomp.sdc.be.dao.titan.TitanOperationStatus;
19 import org.openecomp.sdc.be.datatypes.elements.GetInputValueDataDefinition;
20 import org.openecomp.sdc.be.datatypes.elements.PropertiesOwner;
21 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
22 import org.openecomp.sdc.be.impl.ComponentsUtils;
23 import org.openecomp.sdc.be.model.Component;
24 import org.openecomp.sdc.be.model.ComponentInstancePropInput;
25 import org.openecomp.sdc.be.model.IComponentInstanceConnectedElement;
26 import org.openecomp.sdc.be.model.InputDefinition;
27 import org.openecomp.sdc.be.model.PropertyDefinition;
28 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
29 import org.openecomp.sdc.be.model.operations.impl.DaoStatusConverter;
30 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
31 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
32 import org.openecomp.sdc.exception.ResponseFormat;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.yaml.snakeyaml.Yaml;
37 import com.google.gson.Gson;
39 import fj.data.Either;
41 public abstract class DefaultPropertyDecelerator<PROPERTYOWNER extends PropertiesOwner, PROPERTYTYPE extends PropertyDataDefinition> implements PropertyDecelerator {
43 private static final Logger log = LoggerFactory.getLogger(DefaultPropertyDecelerator.class);
44 private static final short LOOP_PROTECTION_LEVEL = 10;
45 private final Gson gson = new Gson();
46 private ComponentsUtils componentsUtils;
47 private PropertyOperation propertyOperation;
49 public DefaultPropertyDecelerator(ComponentsUtils componentsUtils, PropertyOperation propertyOperation) {
50 this.componentsUtils = componentsUtils;
51 this.propertyOperation = propertyOperation;
55 public Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, String propertiesOwnerId, List<ComponentInstancePropInput> propsToDeclare) {
56 log.debug("#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}", component.getUniqueId(), propertiesOwnerId);
57 return resolvePropertiesOwner(component, propertiesOwnerId)
58 .map(propertyOwner -> declarePropertiesAsInputs(component, propertyOwner, propsToDeclare))
59 .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
62 abstract PROPERTYTYPE createDeclaredProperty(PropertyDataDefinition prop);
64 abstract Either<?, StorageOperationStatus> updatePropertiesValues(Component component, String propertiesOwnerId, List<PROPERTYTYPE> properties);
66 abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(Component component, String propertiesOwnerId);
68 abstract void addPropertiesListToInput(PROPERTYTYPE declaredProp, PropertyDataDefinition originalProp, InputDefinition input);
70 private StorageOperationStatus onPropertiesOwnerNotFound(String componentId, String propertiesOwnerId) {
71 log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId, componentId);
72 return StorageOperationStatus.NOT_FOUND;
75 private Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
76 PropertiesDeclarationData inputsProperties = createInputsAndOverridePropertiesValues(component.getUniqueId(), propertiesOwner, propsToDeclare);
77 return updatePropertiesValues(component, propertiesOwner.getUniqueId(), inputsProperties.getPropertiesToUpdate())
79 .map(updatePropsRes -> inputsProperties.getInputsToCreate());
82 private PropertiesDeclarationData createInputsAndOverridePropertiesValues(String componentId, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
83 List<PROPERTYTYPE> declaredProperties = new ArrayList<>();
84 List<InputDefinition> createdInputs = propsToDeclare.stream()
85 .map(propInput -> declarePropertyInput(componentId, propertiesOwner, declaredProperties, propInput))
86 .collect(Collectors.toList());
87 return new PropertiesDeclarationData(createdInputs, declaredProperties);
90 private InputDefinition declarePropertyInput(String componentId, PropertiesOwner propertiesOwner, List<PROPERTYTYPE> declaredProperties, ComponentInstancePropInput propInput) {
91 PropertyDataDefinition prop = resolveProperty(declaredProperties, propInput);
92 propInput.setOwnerId(null);
93 propInput.setParentUniqueId(null);
94 InputDefinition inputDefinition = createInput(componentId, propertiesOwner, propInput, prop);
95 PROPERTYTYPE declaredProperty = createDeclaredProperty(prop);
96 if(!declaredProperties.contains(declaredProperty)){
97 declaredProperties.add(declaredProperty);
99 addPropertiesListToInput(declaredProperty, prop, inputDefinition);
100 return inputDefinition;
103 private InputDefinition createInput(String componentId, PropertiesOwner propertiesOwner, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
104 String generatedInputName = generateInputName(propertiesOwner.getNormalizedName(), propInput);
105 return createInputFromProperty(componentId, propertiesOwner, generatedInputName, propInput, prop);
108 private String generateInputName(String inputName, ComponentInstancePropInput propInput) {
109 String[] parsedPropNames = propInput.getParsedPropNames();
110 if(parsedPropNames != null){
111 for(String str: parsedPropNames){
112 inputName += "_" + str;
115 inputName += "_" + propInput.getName();
120 private PropertyDataDefinition resolveProperty(List<PROPERTYTYPE> propertiesToCreate, ComponentInstancePropInput propInput) {
121 Optional<PROPERTYTYPE> resolvedProperty = propertiesToCreate.stream()
122 .filter(p -> p.getName().equals(propInput.getName()))
124 return resolvedProperty.isPresent() ? resolvedProperty.get() : propInput;
127 private InputDefinition createInputFromProperty(String componentId, PropertiesOwner propertiesOwner, String inputName, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
128 String propertiesName = propInput.getPropertiesName() ;
129 PropertyDefinition selectedProp = propInput.getInput();
130 String[] parsedPropNames = propInput.getParsedPropNames();
131 InputDefinition input;
132 boolean complexProperty = false;
133 if(propertiesName != null && !propertiesName.isEmpty() && selectedProp != null){
134 complexProperty = true;
135 input = new InputDefinition(selectedProp);
137 input = new InputDefinition(prop);
139 input.setDefaultValue(prop.getValue());
140 input.setName(inputName);
141 input.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, input.getName()));
142 input.setInputPath(propertiesName);
143 input.setInstanceUniqueId(propertiesOwner.getUniqueId());
144 input.setPropertyId(propInput.getUniqueId());
145 changePropertyValueToGetInputValue(inputName, parsedPropNames, input, prop, complexProperty);
146 ((IComponentInstanceConnectedElement)prop).setComponentInstanceId(propertiesOwner.getUniqueId());
147 ((IComponentInstanceConnectedElement)prop).setComponentInstanceName(propertiesOwner.getName());
151 private void changePropertyValueToGetInputValue(String inputName, String[] parsedPropNames, InputDefinition input, PropertyDataDefinition prop, boolean complexProperty) {
152 JSONObject jobject = new JSONObject();
153 if(prop.getValue() == null || prop.getValue().isEmpty()){
156 jobject = createJSONValueForProperty(parsedPropNames.length -1, parsedPropNames, jobject, inputName);
157 prop.setValue(jobject.toJSONString());
161 jobject.put(GET_INPUT, input.getName());
162 prop.setValue(jobject.toJSONString());
168 String value = prop.getValue();
169 Object objValue = new Yaml().load(value);
170 if( objValue instanceof Map || objValue instanceof List){
171 if(!complexProperty){
172 jobject.put(GET_INPUT, input.getName());
173 prop.setValue(jobject.toJSONString());
177 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
178 createInputValue(mappedToscaTemplate, 1, parsedPropNames, inputName);
180 String json = gson.toJson(mappedToscaTemplate);
186 jobject.put(GET_INPUT, input.getName());
187 prop.setValue(jobject.toJSONString());
194 if(CollectionUtils.isEmpty(prop.getGetInputValues())){
195 prop.setGetInputValues(new ArrayList<>());
197 List<GetInputValueDataDefinition> getInputValues = prop.getGetInputValues();
199 GetInputValueDataDefinition getInputValueDataDefinition = new GetInputValueDataDefinition();
200 getInputValueDataDefinition.setInputId(input.getUniqueId());
201 getInputValueDataDefinition.setInputName(input.getName());
202 getInputValues.add(getInputValueDataDefinition);
205 private JSONObject createJSONValueForProperty (int i, String [] parsedPropNames, JSONObject ooj, String inputName){
208 if( i == parsedPropNames.length -1){
209 JSONObject jobProp = new JSONObject();
210 jobProp.put(GET_INPUT, inputName);
211 ooj.put(parsedPropNames[i], jobProp);
213 return createJSONValueForProperty (i, parsedPropNames, ooj, inputName);
215 JSONObject res = new JSONObject();
216 res.put(parsedPropNames[i], ooj);
218 res = createJSONValueForProperty (i, parsedPropNames, res, inputName);
226 private Map<String, Object> createInputValue(Map<String, Object> lhm1, int index, String[] inputNames, String inputName){
227 while(index < inputNames.length){
228 if(lhm1.containsKey(inputNames[index])){
229 Object value = lhm1.get(inputNames[index]);
230 if (value instanceof Map){
231 if(index == inputNames.length -1){
232 ((Map) value).put(GET_INPUT, inputName);
237 return createInputValue((Map)value, index, inputNames, inputName);
240 Map<String, Object> jobProp = new HashMap<>();
241 if(index == inputNames.length -1){
242 jobProp.put(GET_INPUT, inputName);
243 lhm1.put(inputNames[index], jobProp);
246 lhm1.put(inputNames[index], jobProp);
248 return createInputValue(jobProp, index, inputNames, inputName);
252 Map<String, Object> jobProp = new HashMap<>();
253 lhm1.put(inputNames[index], jobProp);
254 if(index == inputNames.length -1){
255 jobProp.put(GET_INPUT, inputName);
259 return createInputValue(jobProp, index, inputNames, inputName);
266 private class PropertiesDeclarationData {
267 private List<InputDefinition> inputsToCreate;
268 private List<PROPERTYTYPE> propertiesToUpdate;
270 PropertiesDeclarationData(List<InputDefinition> inputsToCreate, List<PROPERTYTYPE> propertiesToUpdate) {
271 this.inputsToCreate = inputsToCreate;
272 this.propertiesToUpdate = propertiesToUpdate;
275 List<InputDefinition> getInputsToCreate() {
276 return inputsToCreate;
279 List<PROPERTYTYPE> getPropertiesToUpdate() {
280 return propertiesToUpdate;
284 Either<InputDefinition, ResponseFormat> prepareValueBeforeDelete(InputDefinition inputForDelete, PropertyDataDefinition inputValue, List<String> pathOfComponentInstances) {
285 Either<InputDefinition, ResponseFormat> deleteEither = Either.left(inputForDelete);
286 String value = inputValue.getValue();
287 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
289 resetInputName(mappedToscaTemplate, inputForDelete.getName());
292 if(!mappedToscaTemplate.isEmpty()){
293 Either result = cleanNestedMap(mappedToscaTemplate , true);
294 Map modifiedMappedToscaTemplate = mappedToscaTemplate;
296 modifiedMappedToscaTemplate = (Map)result.left().value();
298 log.warn("Map cleanup failed -> " +result.right().value().toString()); //continue, don't break operation
299 value = gson.toJson(modifiedMappedToscaTemplate);
301 inputValue.setValue(value);
304 List<GetInputValueDataDefinition> getInputsValues = inputValue.getGetInputValues();
305 if(getInputsValues != null && !getInputsValues.isEmpty()){
306 Optional<GetInputValueDataDefinition> op = getInputsValues.stream().filter(gi -> gi.getInputId().equals(inputForDelete.getUniqueId())).findAny();
308 getInputsValues.remove(op.get());
311 inputValue.setGetInputValues(getInputsValues);
313 Either<String, TitanOperationStatus> findDefaultValue = propertyOperation.findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(), inputValue.getDefaultValue());
314 if (findDefaultValue.isRight()) {
315 deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(DaoStatusConverter.convertTitanStatusToStorageStatus(findDefaultValue.right().value()))));
319 String defaultValue = findDefaultValue.left().value();
320 inputValue.setDefaultValue(defaultValue);
321 log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
325 private void resetInputName(Map<String, Object> lhm1, String inputName){
326 for (Map.Entry<String, Object> entry : lhm1.entrySet()) {
327 String key = entry.getKey();
328 Object value = entry.getValue();
329 if (value instanceof String && ((String) value).equalsIgnoreCase(inputName) && key.equals(GET_INPUT)) {
332 } else if (value instanceof Map) {
333 Map<String, Object> subMap = (Map<String, Object>)value;
334 resetInputName(subMap, inputName);
342 private Either cleanNestedMap( Map mappedToscaTemplate , boolean deepClone ){
343 if (MapUtils.isNotEmpty( mappedToscaTemplate ) ){
345 if (!(mappedToscaTemplate instanceof HashMap))
346 return Either.right("expecting mappedToscaTemplate as HashMap ,recieved "+ mappedToscaTemplate.getClass().getSimpleName() );
348 mappedToscaTemplate = (HashMap)((HashMap) mappedToscaTemplate).clone();
350 return Either.left( (Map) cleanEmptyNestedValuesInMap( mappedToscaTemplate , LOOP_PROTECTION_LEVEL ) );
353 log.debug("mappedToscaTemplate is empty ");
354 return Either.right("mappedToscaTemplate is empty ");
358 /* Mutates the object
359 * Tail recurse -> traverse the tosca elements and remove nested empty map properties
360 * this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
362 * @param toscaElement - expected map of tosca values
363 * @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
365 private Object cleanEmptyNestedValuesInMap(Object toscaElement , short loopProtectionLevel ){
366 //region - Stop if map is empty
367 if (loopProtectionLevel<=0 || toscaElement==null || !(toscaElement instanceof Map))
370 //region - Remove empty map entries & return null iff empty map
371 if ( MapUtils.isNotEmpty( (Map)toscaElement ) ) {
373 Set<Object> keysToRemove = new HashSet<>(); // use different set to avoid ConcurrentModificationException
374 for( Object key : ((Map)toscaElement).keySet() ) {
375 Object value = ((Map) toscaElement).get(key);
376 ret = cleanEmptyNestedValuesInMap(value , --loopProtectionLevel );
378 keysToRemove.add(key);
380 Collection set = ((Map) toscaElement).keySet();
381 if (CollectionUtils.isNotEmpty(set))
382 set.removeAll(keysToRemove);
384 if ( isEmptyNestedMap(toscaElement) ) // similar to < if ( MapUtils.isEmpty( (Map)toscaElement ) ) > ,but adds nested map check
393 //@returns true iff map nested maps are all empty
394 //ignores other collection objects
395 private boolean isEmptyNestedMap(Object element){
396 boolean isEmpty = true;
397 if (element != null){
398 if ( element instanceof Map ){
399 if (MapUtils.isEmpty((Map)element))
403 for( Object key : ((Map)(element)).keySet() ){
404 Object value = ((Map)(element)).get(key);
405 isEmpty &= isEmptyNestedMap( value );