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