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.*;
14 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
15 import org.openecomp.sdc.be.model.operations.impl.DaoStatusConverter;
16 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
17 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
18 import org.openecomp.sdc.common.log.wrappers.Logger;
19 import org.openecomp.sdc.exception.ResponseFormat;
20 import org.yaml.snakeyaml.Yaml;
23 import java.util.stream.Collectors;
25 import static org.openecomp.sdc.common.api.Constants.GET_INPUT;
27 public abstract class DefaultPropertyDeclarator<PROPERTYOWNER extends PropertiesOwner, PROPERTYTYPE extends PropertyDataDefinition> implements PropertyDeclarator {
29 private static final Logger log = Logger.getLogger(DefaultPropertyDeclarator.class);
30 private static final short LOOP_PROTECTION_LEVEL = 10;
31 private final Gson gson = new Gson();
32 private ComponentsUtils componentsUtils;
33 private PropertyOperation propertyOperation;
35 public DefaultPropertyDeclarator(ComponentsUtils componentsUtils, PropertyOperation propertyOperation) {
36 this.componentsUtils = componentsUtils;
37 this.propertyOperation = propertyOperation;
41 public Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, String propertiesOwnerId, List<ComponentInstancePropInput> propsToDeclare) {
42 log.debug("#declarePropertiesAsInputs - declaring properties as inputs for component {} from properties owner {}", component.getUniqueId(), propertiesOwnerId);
43 return resolvePropertiesOwner(component, propertiesOwnerId)
44 .map(propertyOwner -> declarePropertiesAsInputs(component, propertyOwner, propsToDeclare))
45 .orElse(Either.right(onPropertiesOwnerNotFound(component.getUniqueId(), propertiesOwnerId)));
48 abstract PROPERTYTYPE createDeclaredProperty(PropertyDataDefinition prop);
50 abstract Either<?, StorageOperationStatus> updatePropertiesValues(Component component, String propertiesOwnerId, List<PROPERTYTYPE> properties);
52 abstract Optional<PROPERTYOWNER> resolvePropertiesOwner(Component component, String propertiesOwnerId);
54 abstract void addPropertiesListToInput(PROPERTYTYPE declaredProp, InputDefinition input);
56 private StorageOperationStatus onPropertiesOwnerNotFound(String componentId, String propertiesOwnerId) {
57 log.debug("#declarePropertiesAsInputs - properties owner {} was not found on component {}", propertiesOwnerId, componentId);
58 return StorageOperationStatus.NOT_FOUND;
61 private Either<List<InputDefinition>, StorageOperationStatus> declarePropertiesAsInputs(Component component, PROPERTYOWNER propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
62 PropertiesDeclarationData inputsProperties = createInputsAndOverridePropertiesValues(component.getUniqueId(), propertiesOwner, propsToDeclare);
63 return updatePropertiesValues(component, propertiesOwner.getUniqueId(), inputsProperties.getPropertiesToUpdate())
65 .map(updatePropsRes -> inputsProperties.getInputsToCreate());
68 private PropertiesDeclarationData createInputsAndOverridePropertiesValues(String componentId, PROPERTYOWNER propertiesOwner, List<ComponentInstancePropInput> propsToDeclare) {
69 List<PROPERTYTYPE> declaredProperties = new ArrayList<>();
70 List<InputDefinition> createdInputs = propsToDeclare.stream()
71 .map(propInput -> declarePropertyInput(componentId, propertiesOwner, declaredProperties, propInput))
72 .collect(Collectors.toList());
73 return new PropertiesDeclarationData(createdInputs, declaredProperties);
76 private InputDefinition declarePropertyInput(String componentId, PROPERTYOWNER propertiesOwner, List<PROPERTYTYPE> declaredProperties, ComponentInstancePropInput propInput) {
77 PropertyDataDefinition prop = resolveProperty(declaredProperties, propInput);
78 InputDefinition inputDefinition = createInput(componentId, propertiesOwner, propInput, prop);
79 PROPERTYTYPE declaredProperty = createDeclaredProperty(prop);
80 if(!declaredProperties.contains(declaredProperty)){
81 declaredProperties.add(declaredProperty);
83 addPropertiesListToInput(declaredProperty, inputDefinition);
84 return inputDefinition;
87 private InputDefinition createInput(String componentId, PROPERTYOWNER propertiesOwner,
88 ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
89 String generatedInputPrefix = propertiesOwner.getNormalizedName();
90 if (propertiesOwner.getUniqueId().equals(propInput.getParentUniqueId())) {
91 //Creating input from property create on self using add property..Do not add the prefix
92 generatedInputPrefix = null;
94 String generatedInputName = generateInputName(generatedInputPrefix, propInput);
95 return createInputFromProperty(componentId, propertiesOwner, generatedInputName, propInput, prop);
98 private String generateInputName(String inputName, ComponentInstancePropInput propInput) {
99 String declaredInputName;
100 String[] parsedPropNames = propInput.getParsedPropNames();
102 if(parsedPropNames != null){
103 declaredInputName = handleInputName(inputName, parsedPropNames);
105 String[] propName = {propInput.getName()};
106 declaredInputName = handleInputName(inputName, propName);
109 return declaredInputName;
112 private String handleInputName(String inputName, String[] parsedPropNames) {
113 StringBuilder prefix = new StringBuilder();
116 if(Objects.isNull(inputName)) {
117 prefix.append(parsedPropNames[0]);
120 prefix.append(inputName);
124 while(startingIndex < parsedPropNames.length){
126 prefix.append(parsedPropNames[startingIndex]);
130 return prefix.toString();
133 private PropertyDataDefinition resolveProperty(List<PROPERTYTYPE> propertiesToCreate, ComponentInstancePropInput propInput) {
134 Optional<PROPERTYTYPE> resolvedProperty = propertiesToCreate.stream()
135 .filter(p -> p.getName().equals(propInput.getName()))
137 return resolvedProperty.isPresent() ? resolvedProperty.get() : propInput;
140 InputDefinition createInputFromProperty(String componentId, PROPERTYOWNER propertiesOwner, String inputName, ComponentInstancePropInput propInput, PropertyDataDefinition prop) {
141 String propertiesName = propInput.getPropertiesName() ;
142 PropertyDefinition selectedProp = propInput.getInput();
143 String[] parsedPropNames = propInput.getParsedPropNames();
144 InputDefinition input;
145 boolean complexProperty = false;
146 if(propertiesName != null && !propertiesName.isEmpty() && selectedProp != null){
147 complexProperty = true;
148 input = new InputDefinition(selectedProp);
149 input.setDefaultValue(selectedProp.getValue());
151 input = new InputDefinition(prop);
152 input.setDefaultValue(prop.getValue());
154 input.setName(inputName);
155 input.setUniqueId(UniqueIdBuilder.buildPropertyUniqueId(componentId, input.getName()));
156 input.setInputPath(propertiesName);
157 input.setInstanceUniqueId(propertiesOwner.getUniqueId());
158 input.setPropertyId(propInput.getUniqueId());
159 changePropertyValueToGetInputValue(inputName, parsedPropNames, input, prop, complexProperty);
161 if(prop instanceof IComponentInstanceConnectedElement) {
162 ((IComponentInstanceConnectedElement) prop)
163 .setComponentInstanceId(propertiesOwner.getUniqueId());
164 ((IComponentInstanceConnectedElement) prop)
165 .setComponentInstanceName(propertiesOwner.getName());
170 private void changePropertyValueToGetInputValue(String inputName, String[] parsedPropNames, InputDefinition input, PropertyDataDefinition prop, boolean complexProperty) {
171 JSONObject jobject = new JSONObject();
172 String value = (String) prop.getValue();
173 if(value == null || value.isEmpty()){
176 jobject = createJSONValueForProperty(parsedPropNames.length -1, parsedPropNames, jobject, inputName);
177 prop.setValue(jobject.toJSONString());
181 jobject.put(GET_INPUT, input.getName());
182 prop.setValue(jobject.toJSONString());
188 //String value = value;
189 Object objValue = new Yaml().load(value);
190 if( objValue instanceof Map || objValue instanceof List){
191 if(!complexProperty){
192 jobject.put(GET_INPUT, input.getName());
193 prop.setValue(jobject.toJSONString());
197 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) objValue;
198 createInputValue(mappedToscaTemplate, 1, parsedPropNames, inputName);
200 String json = gson.toJson(mappedToscaTemplate);
206 jobject.put(GET_INPUT, input.getName());
207 prop.setValue(jobject.toJSONString());
214 if(CollectionUtils.isEmpty(prop.getGetInputValues())){
215 prop.setGetInputValues(new ArrayList<>());
217 List<GetInputValueDataDefinition> getInputValues = prop.getGetInputValues();
219 GetInputValueDataDefinition getInputValueDataDefinition = new GetInputValueDataDefinition();
220 getInputValueDataDefinition.setInputId(input.getUniqueId());
221 getInputValueDataDefinition.setInputName(input.getName());
222 getInputValues.add(getInputValueDataDefinition);
225 private JSONObject createJSONValueForProperty (int i, String [] parsedPropNames, JSONObject ooj, String inputName){
228 if( i == parsedPropNames.length -1){
229 JSONObject jobProp = new JSONObject();
230 jobProp.put(GET_INPUT, inputName);
231 ooj.put(parsedPropNames[i], jobProp);
233 return createJSONValueForProperty (i, parsedPropNames, ooj, inputName);
235 JSONObject res = new JSONObject();
236 res.put(parsedPropNames[i], ooj);
238 res = createJSONValueForProperty (i, parsedPropNames, res, inputName);
246 private Map<String, Object> createInputValue(Map<String, Object> lhm1, int index, String[] inputNames, String inputName){
247 while(index < inputNames.length){
248 if(lhm1.containsKey(inputNames[index])){
249 Object value = lhm1.get(inputNames[index]);
250 if (value instanceof Map){
251 if(index == inputNames.length -1){
252 ((Map) value).put(GET_INPUT, inputName);
257 return createInputValue((Map)value, index, inputNames, inputName);
260 Map<String, Object> jobProp = new HashMap<>();
261 if(index == inputNames.length -1){
262 jobProp.put(GET_INPUT, inputName);
263 lhm1.put(inputNames[index], jobProp);
266 lhm1.put(inputNames[index], jobProp);
268 return createInputValue(jobProp, index, inputNames, inputName);
272 Map<String, Object> jobProp = new HashMap<>();
273 lhm1.put(inputNames[index], jobProp);
274 if(index == inputNames.length -1){
275 jobProp.put(GET_INPUT, inputName);
279 return createInputValue(jobProp, index, inputNames, inputName);
286 private class PropertiesDeclarationData {
287 private List<InputDefinition> inputsToCreate;
288 private List<PROPERTYTYPE> propertiesToUpdate;
290 PropertiesDeclarationData(List<InputDefinition> inputsToCreate, List<PROPERTYTYPE> propertiesToUpdate) {
291 this.inputsToCreate = inputsToCreate;
292 this.propertiesToUpdate = propertiesToUpdate;
295 List<InputDefinition> getInputsToCreate() {
296 return inputsToCreate;
299 List<PROPERTYTYPE> getPropertiesToUpdate() {
300 return propertiesToUpdate;
304 Either<InputDefinition, ResponseFormat> prepareValueBeforeDelete(InputDefinition inputForDelete, PropertyDataDefinition inputValue, List<String> pathOfComponentInstances) {
305 Either<InputDefinition, ResponseFormat> deleteEither = Either.left(inputForDelete);
306 String value = inputValue.getValue();
307 Map<String, Object> mappedToscaTemplate = (Map<String, Object>) new Yaml().load(value);
309 resetInputName(mappedToscaTemplate, inputForDelete.getName());
312 if(!mappedToscaTemplate.isEmpty()){
313 Either result = cleanNestedMap(mappedToscaTemplate , true);
314 Map modifiedMappedToscaTemplate = mappedToscaTemplate;
316 modifiedMappedToscaTemplate = (Map)result.left().value();
318 log.warn("Map cleanup failed -> " +result.right().value().toString()); //continue, don't break operation
319 value = gson.toJson(modifiedMappedToscaTemplate);
321 inputValue.setValue(value);
324 List<GetInputValueDataDefinition> getInputsValues = inputValue.getGetInputValues();
325 if(getInputsValues != null && !getInputsValues.isEmpty()){
326 Optional<GetInputValueDataDefinition> op = getInputsValues.stream().filter(gi -> gi.getInputId().equals(inputForDelete.getUniqueId())).findAny();
328 getInputsValues.remove(op.get());
331 inputValue.setGetInputValues(getInputsValues);
333 Either<String, TitanOperationStatus> findDefaultValue = propertyOperation.findDefaultValueFromSecondPosition(pathOfComponentInstances, inputValue.getUniqueId(), inputValue.getDefaultValue());
334 if (findDefaultValue.isRight()) {
335 deleteEither = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(DaoStatusConverter.convertTitanStatusToStorageStatus(findDefaultValue.right().value()))));
339 String defaultValue = findDefaultValue.left().value();
340 inputValue.setDefaultValue(defaultValue);
341 log.debug("The returned default value in ResourceInstanceProperty is {}", defaultValue);
345 private void resetInputName(Map<String, Object> lhm1, String inputName){
346 for (Map.Entry<String, Object> entry : lhm1.entrySet()) {
347 String key = entry.getKey();
348 Object value = entry.getValue();
349 if (value instanceof String && ((String) value).equalsIgnoreCase(inputName) && key.equals(GET_INPUT)) {
352 } else if (value instanceof Map) {
353 Map<String, Object> subMap = (Map<String, Object>)value;
354 resetInputName(subMap, inputName);
362 private Either cleanNestedMap( Map mappedToscaTemplate , boolean deepClone ){
363 if (MapUtils.isNotEmpty( mappedToscaTemplate ) ){
365 if (!(mappedToscaTemplate instanceof HashMap))
366 return Either.right("expecting mappedToscaTemplate as HashMap ,recieved "+ mappedToscaTemplate.getClass().getSimpleName() );
368 mappedToscaTemplate = (HashMap)((HashMap) mappedToscaTemplate).clone();
370 return Either.left( (Map) cleanEmptyNestedValuesInMap( mappedToscaTemplate , LOOP_PROTECTION_LEVEL ) );
373 log.debug("mappedToscaTemplate is empty ");
374 return Either.right("mappedToscaTemplate is empty ");
378 /* Mutates the object
379 * Tail recurse -> traverse the tosca elements and remove nested empty map properties
380 * this only handles nested maps, other objects are left untouched (even a Set containing a map) since behaviour is unexpected
382 * @param toscaElement - expected map of tosca values
383 * @return mutated @param toscaElement , where empty maps are deleted , return null for empty map.
385 private Object cleanEmptyNestedValuesInMap(Object toscaElement , short loopProtectionLevel ){
386 if (loopProtectionLevel<=0 || toscaElement==null || !(toscaElement instanceof Map))
388 if ( MapUtils.isNotEmpty( (Map)toscaElement ) ) {
390 Set<Object> keysToRemove = new HashSet<>(); // use different set to avoid ConcurrentModificationException
391 for( Object key : ((Map)toscaElement).keySet() ) {
392 Object value = ((Map) toscaElement).get(key);
393 ret = cleanEmptyNestedValuesInMap(value , --loopProtectionLevel );
395 keysToRemove.add(key);
397 Collection set = ((Map) toscaElement).keySet();
398 if (CollectionUtils.isNotEmpty(set))
399 set.removeAll(keysToRemove);
401 if ( isEmptyNestedMap(toscaElement) )
409 //@returns true iff map nested maps are all empty
410 //ignores other collection objects
411 private boolean isEmptyNestedMap(Object element){
412 boolean isEmpty = true;
413 if (element != null){
414 if ( element instanceof Map ){
415 if (MapUtils.isEmpty((Map)element))
419 for( Object key : ((Map)(element)).keySet() ){
420 Object value = ((Map)(element)).get(key);
421 isEmpty &= isEmptyNestedMap( value );