1 package org.openecomp.sdc.be.components.property;
3 import com.google.gson.Gson;
5 import org.apache.commons.collections.CollectionUtils;
6 import org.apache.commons.collections.MapUtils;
7 import org.json.simple.JSONObject;
8 import org.openecomp.sdc.be.dao.titan.TitanOperationStatus;
9 import org.openecomp.sdc.be.datatypes.elements.GetInputValueDataDefinition;
10 import org.openecomp.sdc.be.datatypes.elements.PropertiesOwner;
11 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
12 import org.openecomp.sdc.be.impl.ComponentsUtils;
13 import org.openecomp.sdc.be.model.Component;
14 import org.openecomp.sdc.be.model.ComponentInstancePropInput;
15 import org.openecomp.sdc.be.model.IComponentInstanceConnectedElement;
16 import org.openecomp.sdc.be.model.InputDefinition;
17 import org.openecomp.sdc.be.model.PropertyDefinition;
18 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
19 import org.openecomp.sdc.be.model.operations.impl.DaoStatusConverter;
20 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
21 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
22 import org.openecomp.sdc.exception.ResponseFormat;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25 import org.yaml.snakeyaml.Yaml;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
33 import java.util.Optional;
35 import java.util.stream.Collectors;
37 import static org.openecomp.sdc.common.api.Constants.GET_INPUT;
39 public abstract class DefaultPropertyDecelerator<PROPERTYOWNER extends PropertiesOwner, PROPERTYTYPE extends PropertyDataDefinition> implements PropertyDecelerator {
41 private static final Logger log = LoggerFactory.getLogger(DefaultPropertyDecelerator.class);
42 private static final short LOOP_PROTECTION_LEVEL = 10;
43 private final Gson gson = new Gson();
44 private ComponentsUtils componentsUtils;
45 private PropertyOperation propertyOperation;
47 public DefaultPropertyDecelerator(ComponentsUtils componentsUtils, PropertyOperation propertyOperation) {
48 this.componentsUtils = componentsUtils;
49 this.propertyOperation = propertyOperation;
53 public Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, String propertiesOwnerId, List<ComponentInstancePropInput> propsToDeclare) {
54 log.debug("#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}", component.getUniqueId(), propertiesOwnerId);
55 return resolvePropertiesOwner(component, propertiesOwnerId)
56 .map(propertyOwner -> declarePropertiesAsInputs(component, propertyOwner, propsToDeclare))
57 .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
60 abstract PROPERTYTYPE createDeclaredProperty(PropertyDataDefinition prop);
62 abstract Either<?, StorageOperationStatus> updatePropertiesValues(Component component, String propertiesOwnerId, List<PROPERTYTYPE> properties);
64 abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(Component component, String propertiesOwnerId);
66 abstract void addPropertiesListToInput(PROPERTYTYPE declaredProp, PropertyDataDefinition originalProp, InputDefinition input);
68 private StorageOperationStatus onPropertiesOwnerNotFound(String componentId, String propertiesOwnerId) {
69 log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId, componentId);
70 return StorageOperationStatus.NOT_FOUND;
73 private Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
74 PropertiesDeclarationData inputsProperties = createInputsAndOverridePropertiesValues(component.getUniqueId(), propertiesOwner, propsToDeclare);
75 return updatePropertiesValues(component, propertiesOwner.getUniqueId(), inputsProperties.getPropertiesToUpdate())
77 .map(updatePropsRes -> inputsProperties.getInputsToCreate());
80 private PropertiesDeclarationData createInputsAndOverridePropertiesValues(String componentId, PropertiesOwner propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
81 List<PROPERTYTYPE> declaredProperties = new ArrayList<>();
82 List<InputDefinition> createdInputs = propsToDeclare.stream()
83 .map(propInput -> declarePropertyInput(componentId, propertiesOwner, declaredProperties, propInput))
84 .collect(Collectors.toList());
85 return new PropertiesDeclarationData(createdInputs, declaredProperties);
88 private InputDefinition declarePropertyInput(String componentId, PropertiesOwner propertiesOwner, List<PROPERTYTYPE> declaredProperties, ComponentInstancePropInput propInput) {
89 PropertyDataDefinition prop = resolveProperty(declaredProperties, propInput);
90 propInput.setOwnerId(null);
91 propInput.setParentUniqueId(null);
92 InputDefinition inputDefinition = createInput(componentId, propertiesOwner, propInput, prop);
93 PROPERTYTYPE declaredProperty = createDeclaredProperty(prop);
94 if(!declaredProperties.contains(declaredProperty)){
95 declaredProperties.add(declaredProperty);
97 addPropertiesListToInput(declaredProperty, prop, inputDefinition);
98 return inputDefinition;
101 private InputDefinition createInput(String componentId, PropertiesOwner propertiesOwner, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
102 String generatedInputName = generateInputName(propertiesOwner.getNormalizedName(), propInput);
103 return createInputFromProperty(componentId, propertiesOwner, generatedInputName, propInput, prop);
106 private String generateInputName(String inputName, ComponentInstancePropInput propInput) {
107 String[] parsedPropNames = propInput.getParsedPropNames();
108 if(parsedPropNames != null){
109 for(String str: parsedPropNames){
110 inputName += "_" + str;
113 inputName += "_" + propInput.getName();
118 private PropertyDataDefinition resolveProperty(List<PROPERTYTYPE> propertiesToCreate, ComponentInstancePropInput propInput) {
119 Optional<PROPERTYTYPE> resolvedProperty = propertiesToCreate.stream()
120 .filter(p -> p.getName().equals(propInput.getName()))
122 return resolvedProperty.isPresent() ? resolvedProperty.get() : propInput;
125 private InputDefinition createInputFromProperty(String componentId, PropertiesOwner propertiesOwner, String inputName, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
126 String propertiesName = propInput.getPropertiesName() ;
127 PropertyDefinition selectedProp = propInput.getInput();
128 String[] parsedPropNames = propInput.getParsedPropNames();
129 InputDefinition input;
130 boolean complexProperty = false;
131 if(propertiesName != null && !propertiesName.isEmpty() && selectedProp != null){
132 complexProperty = true;
133 input = new InputDefinition(selectedProp);
135 input = new InputDefinition(prop);
137 input.setDefaultValue(prop.getValue());
138 input.setName(inputName);
139 input.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, input.getName()));
140 input.setInputPath(propertiesName);
141 input.setInstanceUniqueId(propertiesOwner.getUniqueId());
142 input.setPropertyId(propInput.getUniqueId());
143 changePropertyValueToGetInputValue(inputName, parsedPropNames, input, prop, complexProperty);
144 ((IComponentInstanceConnectedElement)prop).setComponentInstanceId(propertiesOwner.getUniqueId());
145 ((IComponentInstanceConnectedElement)prop).setComponentInstanceName(propertiesOwner.getName());
149 private void changePropertyValueToGetInputValue(String inputName, String[] parsedPropNames, InputDefinition input, PropertyDataDefinition prop, boolean complexProperty) {
150 JSONObject jobject = new JSONObject();
151 if(prop.getValue() == null || prop.getValue().isEmpty()){
154 jobject = createJSONValueForProperty(parsedPropNames.length -1, parsedPropNames, jobject, inputName);
155 prop.setValue(jobject.toJSONString());
159 jobject.put(GET_INPUT, input.getName());
160 prop.setValue(jobject.toJSONString());
166 String value = prop.getValue();
167 Object objValue = new Yaml().load(value);
168 if( objValue instanceof Map || objValue instanceof List){
169 if(!complexProperty){
170 jobject.put(GET_INPUT, input.getName());
171 prop.setValue(jobject.toJSONString());
175 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
176 createInputValue(mappedToscaTemplate, 1, parsedPropNames, inputName);
178 String json = gson.toJson(mappedToscaTemplate);
184 jobject.put(GET_INPUT, input.getName());
185 prop.setValue(jobject.toJSONString());
192 if(CollectionUtils.isEmpty(prop.getGetInputValues())){
193 prop.setGetInputValues(new ArrayList<>());
195 List<GetInputValueDataDefinition> getInputValues = prop.getGetInputValues();
197 GetInputValueDataDefinition getInputValueDataDefinition = new GetInputValueDataDefinition();
198 getInputValueDataDefinition.setInputId(input.getUniqueId());
199 getInputValueDataDefinition.setInputName(input.getName());
200 getInputValues.add(getInputValueDataDefinition);
203 private JSONObject createJSONValueForProperty (int i, String [] parsedPropNames, JSONObject ooj, String inputName){
206 if( i == parsedPropNames.length -1){
207 JSONObject jobProp = new JSONObject();
208 jobProp.put(GET_INPUT, inputName);
209 ooj.put(parsedPropNames[i], jobProp);
211 return createJSONValueForProperty (i, parsedPropNames, ooj, inputName);
213 JSONObject res = new JSONObject();
214 res.put(parsedPropNames[i], ooj);
216 res = createJSONValueForProperty (i, parsedPropNames, res, inputName);
224 private Map<String, Object> createInputValue(Map<String, Object> lhm1, int index, String[] inputNames, String inputName){
225 while(index < inputNames.length){
226 if(lhm1.containsKey(inputNames[index])){
227 Object value = lhm1.get(inputNames[index]);
228 if (value instanceof Map){
229 if(index == inputNames.length -1){
230 ((Map) value).put(GET_INPUT, inputName);
235 return createInputValue((Map)value, index, inputNames, inputName);
238 Map<String, Object> jobProp = new HashMap<>();
239 if(index == inputNames.length -1){
240 jobProp.put(GET_INPUT, inputName);
241 lhm1.put(inputNames[index], jobProp);
244 lhm1.put(inputNames[index], jobProp);
246 return createInputValue(jobProp, index, inputNames, inputName);
250 Map<String, Object> jobProp = new HashMap<>();
251 lhm1.put(inputNames[index], jobProp);
252 if(index == inputNames.length -1){
253 jobProp.put(GET_INPUT, inputName);
257 return createInputValue(jobProp, index, inputNames, inputName);
264 private class PropertiesDeclarationData {
265 private List<InputDefinition> inputsToCreate;
266 private List<PROPERTYTYPE> propertiesToUpdate;
268 PropertiesDeclarationData(List<InputDefinition> inputsToCreate, List<PROPERTYTYPE> propertiesToUpdate) {
269 this.inputsToCreate = inputsToCreate;
270 this.propertiesToUpdate = propertiesToUpdate;
273 List<InputDefinition> getInputsToCreate() {
274 return inputsToCreate;
277 List<PROPERTYTYPE> getPropertiesToUpdate() {
278 return propertiesToUpdate;
282 Either<InputDefinition, ResponseFormat> prepareValueBeforeDelete(InputDefinition inputForDelete, PropertyDataDefinition inputValue, List<String> pathOfComponentInstances) {
283 Either<InputDefinition, ResponseFormat> deleteEither = Either.left(inputForDelete);
284 String value = inputValue.getValue();
285 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
287 resetInputName(mappedToscaTemplate, inputForDelete.getName());
290 if(!mappedToscaTemplate.isEmpty()){
291 Either result = cleanNestedMap(mappedToscaTemplate , true);
292 Map modifiedMappedToscaTemplate = mappedToscaTemplate;
294 modifiedMappedToscaTemplate = (Map)result.left().value();
296 log.warn("Map cleanup failed -> " +result.right().value().toString()); //continue, don't break operation
297 value = gson.toJson(modifiedMappedToscaTemplate);
299 inputValue.setValue(value);
302 List<GetInputValueDataDefinition> getInputsValues = inputValue.getGetInputValues();
303 if(getInputsValues != null && !getInputsValues.isEmpty()){
304 Optional<GetInputValueDataDefinition> op = getInputsValues.stream().filter(gi -> gi.getInputId().equals(inputForDelete.getUniqueId())).findAny();
306 getInputsValues.remove(op.get());
309 inputValue.setGetInputValues(getInputsValues);
311 Either<String, TitanOperationStatus> findDefaultValue = propertyOperation.findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(), inputValue.getDefaultValue());
312 if (findDefaultValue.isRight()) {
313 deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(DaoStatusConverter.convertTitanStatusToStorageStatus(findDefaultValue.right().value()))));
317 String defaultValue = findDefaultValue.left().value();
318 inputValue.setDefaultValue(defaultValue);
319 log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
323 private void resetInputName(Map<String, Object> lhm1, String inputName){
324 for (Map.Entry<String, Object> entry : lhm1.entrySet()) {
325 String key = entry.getKey();
326 Object value = entry.getValue();
327 if (value instanceof String && ((String) value).equalsIgnoreCase(inputName) && key.equals(GET_INPUT)) {
330 } else if (value instanceof Map) {
331 Map<String, Object> subMap = (Map<String, Object>)value;
332 resetInputName(subMap, inputName);
340 private Either cleanNestedMap( Map mappedToscaTemplate , boolean deepClone ){
341 if (MapUtils.isNotEmpty( mappedToscaTemplate ) ){
343 if (!(mappedToscaTemplate instanceof HashMap))
344 return Either.right("expecting mappedToscaTemplate as HashMap ,recieved "+ mappedToscaTemplate.getClass().getSimpleName() );
346 mappedToscaTemplate = (HashMap)((HashMap) mappedToscaTemplate).clone();
348 return Either.left( (Map) cleanEmptyNestedValuesInMap( mappedToscaTemplate , LOOP_PROTECTION_LEVEL ) );
351 log.debug("mappedToscaTemplate is empty ");
352 return Either.right("mappedToscaTemplate is empty ");
356 /* Mutates the object
357 * Tail recurse -> traverse the tosca elements and remove nested empty map properties
358 * this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
360 * @param toscaElement - expected map of tosca values
361 * @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
363 private Object cleanEmptyNestedValuesInMap(Object toscaElement , short loopProtectionLevel ){
364 //region - Stop if map is empty
365 if (loopProtectionLevel<=0 || toscaElement==null || !(toscaElement instanceof Map))
368 //region - Remove empty map entries & return null iff empty map
369 if ( MapUtils.isNotEmpty( (Map)toscaElement ) ) {
371 Set<Object> keysToRemove = new HashSet<>(); // use different set to avoid ConcurrentModificationException
372 for( Object key : ((Map)toscaElement).keySet() ) {
373 Object value = ((Map) toscaElement).get(key);
374 ret = cleanEmptyNestedValuesInMap(value , --loopProtectionLevel );
376 keysToRemove.add(key);
378 Collection set = ((Map) toscaElement).keySet();
379 if (CollectionUtils.isNotEmpty(set))
380 set.removeAll(keysToRemove);
382 if ( isEmptyNestedMap(toscaElement) ) // similar to < if ( MapUtils.isEmpty( (Map)toscaElement ) ) > ,but adds nested map check
391 //@returns true iff map nested maps are all empty
392 //ignores other collection objects
393 private boolean isEmptyNestedMap(Object element){
394 boolean isEmpty = true;
395 if (element != null){
396 if ( element instanceof Map ){
397 if (MapUtils.isEmpty((Map)element))
401 for( Object key : ((Map)(element)).keySet() ){
402 Object value = ((Map)(element)).get(key);
403 isEmpty &= isEmptyNestedMap( value );