Support complex types in artifact properties
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / tosca / InterfacesOperationsConverter.java
1 /*
2  * Copyright © 2016-2020 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.openecomp.sdc.be.tosca;
17
18 import static org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum.DEFAULT;
19 import static org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum.INPUTS;
20 import static org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum.OPERATIONS;
21
22 import com.fasterxml.jackson.annotation.JsonInclude;
23 import com.fasterxml.jackson.databind.DeserializationFeature;
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.fasterxml.jackson.databind.module.SimpleModule;
26 import com.google.gson.Gson;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Objects;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 import org.apache.commons.collections.MapUtils;
36 import org.apache.commons.collections4.CollectionUtils;
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.commons.lang3.math.NumberUtils;
39 import org.openecomp.sdc.be.datatypes.elements.InputDataDefinition;
40 import org.openecomp.sdc.be.datatypes.elements.OperationDataDefinition;
41 import org.openecomp.sdc.be.datatypes.elements.OperationInputDefinition;
42 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
43 import org.openecomp.sdc.be.model.Component;
44 import org.openecomp.sdc.be.model.ComponentInstance;
45 import org.openecomp.sdc.be.model.DataTypeDefinition;
46 import org.openecomp.sdc.be.model.InterfaceDefinition;
47 import org.openecomp.sdc.be.model.Product;
48 import org.openecomp.sdc.be.model.PropertyDefinition;
49 import org.openecomp.sdc.be.tosca.PropertyConvertor.PropertyType;
50 import org.openecomp.sdc.be.tosca.model.ToscaArtifactDefinition;
51 import org.openecomp.sdc.be.tosca.model.ToscaInput;
52 import org.openecomp.sdc.be.tosca.model.ToscaInterfaceDefinition;
53 import org.openecomp.sdc.be.tosca.model.ToscaInterfaceNodeType;
54 import org.openecomp.sdc.be.tosca.model.ToscaInterfaceOperationImplementation;
55 import org.openecomp.sdc.be.tosca.model.ToscaLifecycleOperationDefinition;
56 import org.openecomp.sdc.be.tosca.model.ToscaNodeType;
57 import org.openecomp.sdc.be.tosca.model.ToscaProperty;
58 import org.openecomp.sdc.be.tosca.model.ToscaPropertyAssignment;
59 import org.openecomp.sdc.be.tosca.model.ToscaPropertyAssignmentJsonSerializer;
60 import org.openecomp.sdc.be.tosca.utils.OperationArtifactUtil;
61 import org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum;
62 import org.openecomp.sdc.tosca.datatypes.ToscaFunctions;
63 import org.springframework.beans.factory.annotation.Autowired;
64 import org.springframework.stereotype.Service;
65
66 @Service
67 public class InterfacesOperationsConverter {
68
69     public static final String SELF = "SELF";
70     private static final String DERIVED_FROM_STANDARD_INTERFACE = "tosca.interfaces.node.lifecycle.Standard";
71     private static final String DERIVED_FROM_BASE_DEFAULT = "org.openecomp.interfaces.node.lifecycle.";
72     private static final String DEFAULT_HAS_UNDERSCORE = "_default";
73     private static final String DOT = ".";
74     private static final String DEFAULTP = "defaultp";
75     private static final String LOCAL_INTERFACE_TYPE = "Local";
76     private final PropertyConvertor propertyConvertor;
77
78     @Autowired
79     public InterfacesOperationsConverter(final PropertyConvertor propertyConvertor) {
80         this.propertyConvertor = propertyConvertor;
81     }
82
83     /**
84      * Creates the interface_types element.
85      *
86      * @param component to work on
87      * @return the added element
88      */
89     public static Map<String, Object> addInterfaceTypeElement(Component component, List<String> allInterfaceTypes) {
90         if (component instanceof Product) {
91             return null;
92         }
93         final Map<String, InterfaceDefinition> interfaces = component.getInterfaces();
94         if (MapUtils.isEmpty(interfaces)) {
95             return null;
96         }
97         Map<String, Object> toscaInterfaceTypes = new HashMap<>();
98         for (InterfaceDefinition interfaceDefinition : interfaces.values()) {
99             boolean isInterfaceTypeExistInGlobalType = allInterfaceTypes.stream()
100                 .anyMatch(type -> type.equalsIgnoreCase(interfaceDefinition.getType()));
101             if (!isInterfaceTypeExistInGlobalType) {
102                 ToscaInterfaceNodeType toscaInterfaceType = new ToscaInterfaceNodeType();
103                 toscaInterfaceType.setDerived_from(DERIVED_FROM_STANDARD_INTERFACE);
104                 final Map<String, OperationDataDefinition> operations = interfaceDefinition.getOperations();
105                 Map<String, Object> toscaOperations = new HashMap<>();
106                 for (Map.Entry<String, OperationDataDefinition> operationEntry : operations.entrySet()) {
107                     toscaOperations.put(operationEntry.getValue().getName(), null);
108                 }
109                 toscaInterfaceType.setOperations(toscaOperations);
110                 Map<String, Object> interfacesAsMap = getObjectAsMap(toscaInterfaceType);
111                 Map<String, Object> operationsMap = (Map<String, Object>) interfacesAsMap.remove(OPERATIONS.getElementName());
112                 interfacesAsMap.putAll(operationsMap);
113                 toscaInterfaceTypes.put(getInterfaceType(component, LOCAL_INTERFACE_TYPE), interfacesAsMap);
114             }
115         }
116         return MapUtils.isNotEmpty(toscaInterfaceTypes) ? toscaInterfaceTypes : null;
117     }
118
119     private static Object getDefaultValue(Map<String, Object> inputValueMap) {
120         Object defaultValue = null;
121         for (Map.Entry<String, Object> operationEntry : inputValueMap.entrySet()) {
122             final Object value = operationEntry.getValue();
123             if (value instanceof Map) {
124                 getDefaultValue((Map<String, Object>) value);
125             }
126             final String key = operationEntry.getKey();
127             if (key.equals(DEFAULTP)) {
128                 defaultValue = inputValueMap.remove(key);
129             }
130         }
131         return defaultValue;
132     }
133
134     //Remove input type and copy default value directly into the proxy node template from the node type
135     private static void handleOperationInputValue(Map<String, Object> operationsMap, String parentKey) {
136         for (Map.Entry<String, Object> operationEntry : operationsMap.entrySet()) {
137             final Object value = operationEntry.getValue();
138             final String key = operationEntry.getKey();
139             if (value instanceof Map) {
140                 if (INPUTS.getElementName().equals(parentKey)) {
141                     Object defaultValue = getDefaultValue((Map<String, Object>) value);
142                     operationsMap.put(key, defaultValue);
143                 } else {
144                     handleOperationInputValue((Map<String, Object>) value, key);
145                 }
146             }
147         }
148     }
149
150     private static String getLastPartOfName(String toscaResourceName) {
151         return toscaResourceName.substring(toscaResourceName.lastIndexOf(DOT) + 1);
152     }
153
154     private static boolean isArtifactPresent(final OperationDataDefinition operationDataDefinition) {
155         return operationDataDefinition.getImplementation() != null && operationDataDefinition.getImplementation().getArtifactName() != null;
156     }
157
158     private static String getInputValue(final OperationInputDefinition input) {
159         String inputValue = input.getValue() == null ? input.getToscaDefaultValue(): input.getValue();
160         if (inputValue != null && inputValue.contains(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName())) {
161             Gson gson = new Gson();
162             Map<String, List<String>> consumptionValue = gson.fromJson(inputValue, Map.class);
163             List<String> mappedOutputValue = consumptionValue.get(ToscaFunctions.GET_OPERATION_OUTPUT.getFunctionName());
164             //Extract the interface name from the interface type
165             String interfaceType = mappedOutputValue.get(1);
166             String interfaceName = interfaceType.substring(interfaceType.lastIndexOf('.') + 1);
167             mappedOutputValue.remove(1);
168             mappedOutputValue.add(1, interfaceName);
169             inputValue = gson.toJson(consumptionValue);
170         }
171         return inputValue;
172     }
173
174     private static String getInterfaceType(Component component, String interfaceType) {
175         if (LOCAL_INTERFACE_TYPE.equals(interfaceType)) {
176             return DERIVED_FROM_BASE_DEFAULT + component.getComponentMetadataDefinition().getMetadataDataDefinition().getSystemName();
177         }
178         return interfaceType;
179     }
180
181     private static Map<String, Object> getObjectAsMap(final Object obj) {
182         final Map<String, Object> objectAsMap;
183         if (obj instanceof Map) {
184             objectAsMap = (Map<String, Object>) obj;
185         } else {
186             final ObjectMapper objectMapper = new ObjectMapper();
187             final SimpleModule module = new SimpleModule("ToscaPropertyAssignmentSerializer");
188             module.addSerializer(ToscaPropertyAssignment.class, new ToscaPropertyAssignmentJsonSerializer());
189             objectMapper.registerModule(module);
190             if (obj instanceof ToscaInterfaceDefinition) {
191                 //Prevent empty field serialization in interface definition
192                 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
193             }
194             objectAsMap = objectMapper.convertValue(obj, Map.class);
195         }
196
197         final String defaultEntry = DEFAULT.getElementName();
198         if (objectAsMap.containsKey(defaultEntry)) {
199             objectAsMap.put(DEFAULT_HAS_UNDERSCORE, objectAsMap.remove(defaultEntry));
200         }
201         return objectAsMap;
202     }
203
204     /**
205      * Adds the 'interfaces' element to the node type provided.
206      *
207      * @param component to work on
208      * @param nodeType  to which the interfaces element will be added
209      */
210     public void addInterfaceDefinitionElement(Component component, ToscaNodeType nodeType, Map<String, DataTypeDefinition> dataTypes,
211                                               boolean isAssociatedComponent) {
212         if (component instanceof Product) {
213             return;
214         }
215         final Map<String, InterfaceDefinition> interfaces = component.getInterfaces();
216         if (MapUtils.isEmpty(interfaces)) {
217             return;
218         }
219         Map<String, Object> toscaInterfaceDefinitions = getInterfacesMap(component, dataTypes, isAssociatedComponent);
220         if (MapUtils.isNotEmpty(toscaInterfaceDefinitions)) {
221             nodeType.setInterfaces(toscaInterfaceDefinitions);
222         }
223     }
224
225     private Map<String, Object> getInterfacesMap(Component component, Map<String, DataTypeDefinition> dataTypes, boolean isAssociatedComponent) {
226         return getInterfacesMap(component, null, component.getInterfaces(), dataTypes, isAssociatedComponent, false);
227     }
228
229     public Map<String, Object> getInterfacesMap(final Component component, final ComponentInstance componentInstance,
230                                                 final Map<String, InterfaceDefinition> interfaces, final Map<String, DataTypeDefinition> dataTypes,
231                                                 final boolean isAssociatedComponent, final boolean isServiceProxyInterface) {
232         if (MapUtils.isEmpty(interfaces)) {
233             return null;
234         }
235         final Map<String, Object> toscaInterfaceDefinitions = new HashMap<>();
236         for (InterfaceDefinition interfaceDefinition : interfaces.values()) {
237             handleInterfaceOperations(component, componentInstance, dataTypes, isAssociatedComponent, isServiceProxyInterface,
238                 toscaInterfaceDefinitions, interfaceDefinition);
239         }
240         return toscaInterfaceDefinitions;
241     }
242
243     public Map<String, Object> getInterfacesMapFromComponentInstance(final Component component, final ComponentInstance componentInstance,
244                                                                      final Map<String, DataTypeDefinition> dataTypes,
245                                                                      final boolean isAssociatedComponent, final boolean isServiceProxyInterface) {
246         final Map<String, Object> toscaInterfaceDefinitions = new HashMap<>();
247         final ObjectMapper objectMapper = new ObjectMapper();
248         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
249         for (final Map.Entry<String, Object> interfaceEntry : componentInstance.getInterfaces().entrySet()) {
250             final InterfaceDefinition interfaceDefinition = objectMapper.convertValue(interfaceEntry.getValue(), InterfaceDefinition.class);
251             handleInterfaceOperations(component, componentInstance, dataTypes, isAssociatedComponent, isServiceProxyInterface,
252                 toscaInterfaceDefinitions, interfaceDefinition);
253         }
254         return toscaInterfaceDefinitions;
255     }
256
257     private void handleInterfaceOperations(final Component component, final ComponentInstance componentInstance,
258                                            final Map<String, DataTypeDefinition> dataTypes, final boolean isAssociatedComponent,
259                                            final boolean isServiceProxyInterface, final Map<String, Object> toscaInterfaceDefinitions,
260                                            final InterfaceDefinition interfaceDefinition) {
261         final String interfaceType;
262         if (componentInstance != null && LOCAL_INTERFACE_TYPE.equals(interfaceDefinition.getType())) {
263             interfaceType = DERIVED_FROM_BASE_DEFAULT + componentInstance.getSourceModelName();
264         } else {
265             interfaceType = getInterfaceType(component, interfaceDefinition.getType());
266         }
267         final ToscaInterfaceDefinition toscaInterfaceDefinition = new ToscaInterfaceDefinition();
268         if (componentInstance == null) {
269             toscaInterfaceDefinition.setType(interfaceType);
270         }
271         final Map<String, OperationDataDefinition> operations = interfaceDefinition.getOperations();
272         final Map<String, Object> toscaOperationMap = new HashMap<>();
273         for (final Entry<String, OperationDataDefinition> operationEntry : operations.entrySet()) {
274             final ToscaLifecycleOperationDefinition toscaLifecycleOperationDefinition = new ToscaLifecycleOperationDefinition();
275             handleInterfaceOperationImplementation(component, componentInstance, isAssociatedComponent, operationEntry.getValue(),
276                 toscaLifecycleOperationDefinition, dataTypes);
277             toscaLifecycleOperationDefinition.setDescription(operationEntry.getValue().getDescription());
278             fillToscaOperationInputs(operationEntry.getValue(), dataTypes, toscaLifecycleOperationDefinition);
279             toscaOperationMap.put(operationEntry.getValue().getName(), toscaLifecycleOperationDefinition);
280         }
281         toscaInterfaceDefinition.setOperations(toscaOperationMap);
282         final Map<String, Object> interfaceInputMap = createInterfaceInputMap(interfaceDefinition, dataTypes);
283         if (!interfaceInputMap.isEmpty()) {
284             toscaInterfaceDefinition.setInputs(interfaceInputMap);
285         }
286         final Map<String, Object> interfaceDefinitionAsMap = getObjectAsMap(toscaInterfaceDefinition);
287         if (interfaceDefinitionAsMap.containsKey(INPUTS.getElementName())) {
288             handleDefaults((Map<String, Object>) interfaceDefinitionAsMap.get(INPUTS.getElementName()));
289         }
290         final Map<String, Object> operationsMap = (Map<String, Object>) interfaceDefinitionAsMap.remove(OPERATIONS.getElementName());
291         handleOperationInputValue(operationsMap, interfaceType);
292         interfaceDefinitionAsMap.putAll(operationsMap);
293         toscaInterfaceDefinitions.put(getLastPartOfName(interfaceType), interfaceDefinitionAsMap);
294     }
295
296     private void handleInterfaceOperationImplementation(final Component component, final ComponentInstance componentInstance,
297                                                         final boolean isAssociatedComponent,
298                                                         final OperationDataDefinition operationDataDefinition,
299                                                         final ToscaLifecycleOperationDefinition toscaOperation,
300                                                         final Map<String, DataTypeDefinition> dataTypes) {
301         final String operationArtifactPath;
302         final ToscaInterfaceOperationImplementation toscaInterfaceOperationImplementation = new ToscaInterfaceOperationImplementation();
303         toscaInterfaceOperationImplementation.setPrimary(new ToscaArtifactDefinition());
304         final ToscaArtifactDefinition toscaArtifactDefinition = toscaInterfaceOperationImplementation.getPrimary();
305         if (isArtifactPresent(operationDataDefinition) && StringUtils.isNotEmpty(operationDataDefinition.getImplementation().getArtifactName())) {
306             operationArtifactPath = OperationArtifactUtil
307                 .createOperationArtifactPath(component, componentInstance, operationDataDefinition, isAssociatedComponent);
308             toscaArtifactDefinition.setFile(operationArtifactPath);
309             toscaArtifactDefinition.setArtifact_version(!operationDataDefinition.getImplementation().getArtifactVersion()
310                 .equals(NumberUtils.INTEGER_ZERO.toString()) ? operationDataDefinition.getImplementation().getArtifactVersion() : null);
311             toscaArtifactDefinition.setType(operationDataDefinition.getImplementation().getArtifactType());
312             final Map<String, ToscaPropertyAssignment> propertiesMap = handleImplementationProperties(operationDataDefinition, dataTypes);
313             if (!propertiesMap.isEmpty()) {
314                 toscaArtifactDefinition.setProperties(propertiesMap);
315             }
316             toscaOperation.setImplementation(
317                 toscaArtifactDefinition.getType() != null ? toscaInterfaceOperationImplementation : operationArtifactPath);
318         } else {
319             toscaArtifactDefinition.setFile(operationDataDefinition.getImplementation().getArtifactName());
320             toscaOperation.setImplementation(toscaInterfaceOperationImplementation);
321         }
322     }
323
324     private Map<String, ToscaPropertyAssignment> handleImplementationProperties(final OperationDataDefinition operationDataDefinition,
325                                                                                 final Map<String, DataTypeDefinition> dataTypes) {
326         if (operationDataDefinition.getImplementation() == null) {
327             return new HashMap<>();
328         }
329
330         final List<PropertyDataDefinition> properties = operationDataDefinition.getImplementation().getProperties();
331         if (CollectionUtils.isEmpty(properties)) {
332             return new HashMap<>();
333         }
334
335         final Map<String, ToscaPropertyAssignment> propertiesMap = new HashMap<>();
336         properties.stream()
337             .filter(propertyDataDefinition -> StringUtils.isNotEmpty(propertyDataDefinition.getValue()))
338             .forEach(propertyDataDefinition -> {
339                     final String propertyValue =
340                         propertyDataDefinition.getValue() != null ? propertyDataDefinition.getValue() : propertyDataDefinition.getDefaultValue();
341                     final ToscaPropertyAssignment toscaPropertyAssignment = new ToscaPropertyAssignment();
342                     toscaPropertyAssignment.setValue(propertyConvertor.convertToToscaObject(propertyDataDefinition, propertyValue, dataTypes, false));
343                     propertiesMap.put(propertyDataDefinition.getName(), toscaPropertyAssignment);
344                 }
345             );
346
347         return propertiesMap;
348     }
349
350     public void removeInterfacesWithoutOperations(final Map<String, Object> interfaceMap) {
351         if (MapUtils.isEmpty(interfaceMap)) {
352             return;
353         }
354         final Set<String> emptyInterfaces = interfaceMap.entrySet().stream().filter(entry -> {
355             final Object value = entry.getValue();
356             if (value instanceof ToscaInterfaceDefinition) {
357                 final ToscaInterfaceDefinition interfaceDefinition = (ToscaInterfaceDefinition) value;
358                 return MapUtils.isEmpty(interfaceDefinition.getOperations());
359             } else if (value instanceof Map) {
360                 final Map<String, Object> interfaceDefMap = (Map<String, Object>) value;
361                 return MapUtils.isEmpty(interfaceDefMap);
362             }
363             return false;
364         }).map(Entry::getKey).collect(Collectors.toSet());
365         emptyInterfaces.forEach(interfaceMap::remove);
366     }
367
368     private Map<String, Object> createInterfaceInputMap(final InterfaceDefinition interfaceDefinition,
369                                                         final Map<String, DataTypeDefinition> allDataTypeMap) {
370         final Map<String, InputDataDefinition> inputMap = interfaceDefinition.getInputs();
371         if (MapUtils.isEmpty(inputMap)) {
372             return Collections.emptyMap();
373         }
374         final Map<String, Object> toscaInterfaceInputMap = new HashMap<>();
375         for (final Entry<String, InputDataDefinition> inputEntry : inputMap.entrySet()) {
376             final InputDataDefinition inputDataDefinition = inputEntry.getValue();
377             final ToscaProperty toscaProperty = propertyConvertor
378                 .convertProperty(allDataTypeMap, new PropertyDefinition(inputDataDefinition), PropertyType.INPUT);
379             toscaInterfaceInputMap.put(inputEntry.getKey(), new ToscaInput(toscaProperty));
380         }
381         return toscaInterfaceInputMap;
382     }
383
384     /*
385      * workaround for : currently "defaultp" is not being converted to "default" by the relevant code in
386      * ToscaExportHandler so, any string Map key named "defaultp" will have its named changed to "default"
387      * @param operationsMap the map to update
388      */
389     private void handleDefaults(Map<String, Object> operationsMap) {
390         for (Map.Entry<String, Object> operationEntry : operationsMap.entrySet()) {
391             final Object value = operationEntry.getValue();
392             if (value instanceof Map) {
393                 handleDefaults((Map<String, Object>) value);
394             }
395             final String key = operationEntry.getKey();
396             if (key.equals(DEFAULTP)) {
397                 Object removed = operationsMap.remove(key);
398                 operationsMap.put(ToscaTagNamesEnum.DEFAULT.getElementName(), removed);
399             }
400         }
401     }
402
403     private void fillToscaOperationInputs(OperationDataDefinition operation, Map<String, DataTypeDefinition> dataTypes,
404                                           ToscaLifecycleOperationDefinition toscaOperation) {
405         if (Objects.isNull(operation.getInputs()) || operation.getInputs().isEmpty()) {
406             toscaOperation.setInputs(null);
407             return;
408         }
409         Map<String, ToscaProperty> toscaInputs = new HashMap<>();
410         for (OperationInputDefinition input : operation.getInputs().getListToscaDataDefinition()) {
411             ToscaProperty toscaInput = new ToscaProperty();
412             toscaInput.setDescription(input.getDescription());
413             toscaInput.setType(input.getType());
414             toscaInput.setRequired(input.isRequired());
415             toscaInput.setDefaultp(propertyConvertor.convertToToscaObject(input, getInputValue(input), dataTypes, false));
416             toscaInputs.put(input.getName(), toscaInput);
417         }
418         toscaOperation.setInputs(toscaInputs);
419     }
420
421 }