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