Fix 'Substitution Node not updated during import'-bug
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / csar / ServiceCsarInfo.java
1 /*
2  * -
3  *  ============LICENSE_START=======================================================
4  *  Copyright (C) 2022 Nordix Foundation.
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *       http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.sdc.be.components.csar;
23
24 import static org.openecomp.sdc.be.components.impl.ImportUtils.Constants.DEFAULT_ICON;
25 import static org.openecomp.sdc.be.components.impl.ImportUtils.findFirstToscaStringElement;
26 import static org.openecomp.sdc.be.components.impl.ImportUtils.findToscaElement;
27
28 import fj.data.Either;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39
40 import org.apache.commons.collections.CollectionUtils;
41 import org.apache.commons.collections.MapUtils;
42 import org.openecomp.sdc.be.components.impl.ImportUtils;
43 import org.openecomp.sdc.be.components.impl.ImportUtils.ResultStatusEnum;
44 import org.openecomp.sdc.be.components.impl.ImportUtils.ToscaElementTypeEnum;
45 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
46 import org.openecomp.sdc.be.model.NodeTypeDefinition;
47 import org.openecomp.sdc.be.model.NodeTypeInfo;
48 import org.openecomp.sdc.be.model.NodeTypeMetadata;
49 import org.openecomp.sdc.be.model.NullNodeTypeMetadata;
50 import org.openecomp.sdc.be.model.User;
51 import org.openecomp.sdc.be.model.category.CategoryDefinition;
52 import org.openecomp.sdc.be.model.category.SubCategoryDefinition;
53 import org.openecomp.sdc.be.model.operations.impl.ModelOperation;
54 import org.openecomp.sdc.be.utils.TypeUtils;
55 import org.openecomp.sdc.be.utils.TypeUtils.ToscaTagNamesEnum;
56 import org.openecomp.sdc.common.api.Constants;
57 import org.openecomp.sdc.common.log.wrappers.Logger;
58 import org.yaml.snakeyaml.Yaml;
59
60 /**
61  * Provides access to the contents of a Service CSAR
62  */
63 public class ServiceCsarInfo extends CsarInfo {
64
65     private static final Logger log = Logger.getLogger(ServiceCsarInfo.class);
66     private final Map<String, Map<String, Object>> mainTemplateImports;
67     private List<NodeTypeDefinition> nodeTypeDefinitions;
68     private final String model;
69     private final ModelOperation modelOperation;
70
71     public ServiceCsarInfo(final User modifier, final String csarUUID, final Map<String, byte[]> csar,
72                            final String vfResourceName, final String model,
73                            final String mainTemplateName, final String mainTemplateContent, final boolean isUpdate, final ModelOperation modelOperation) {
74         super(modifier, csarUUID, csar, vfResourceName, mainTemplateName, mainTemplateContent, isUpdate);
75         this.model = model;
76         this.modelOperation = modelOperation;
77         final Path mainTemplateDir = Paths.get(getMainTemplateName().substring(0, getMainTemplateName().lastIndexOf('/') + 1));
78         final Collection<Path> filesHandled = new HashSet<>();
79         filesHandled.add(Paths.get(mainTemplateName));
80         this.mainTemplateImports = getTemplateImports(csar, new Yaml().load(mainTemplateContent), mainTemplateDir, filesHandled);
81     }
82
83     private Map<String, Map<String, Object>> getTemplateImports(final Map<String, byte[]> csar, Map<String, Object> mappedToscaMainTemplate,
84                                                                 final Path fileParentDir, final Collection<Path> filesHandled) {
85         final Map<String, Map<String, Object>> templateImports = new HashMap<>();
86
87         final List<Path> importFilePaths = getTemplateImportFilePaths(mappedToscaMainTemplate, fileParentDir);
88
89         importFilePaths.stream().filter(path -> !filesHandled.contains(path)).forEach(
90             importFilePath -> {
91                 final String importFilePathString = importFilePath.toString();
92                 final byte[] importFile = csar.get(importFilePathString);
93                 if (importFile != null) {
94                     filesHandled.add(importFilePath);
95                     final Map<String, Object> mappedImportFile = new Yaml().load(new String(importFile));
96                     templateImports.put(importFilePathString, mappedImportFile);
97                     templateImports.putAll(getTemplateImports(csar, mappedImportFile, importFilePath.getParent(), filesHandled));
98                 } else {
99                     log.warn("Import {} cannot be found in CSAR", importFilePathString);
100                 }
101             });
102
103         return templateImports;
104     }
105
106     @SuppressWarnings({"unchecked", "rawtypes"})
107     private List<Path> getTemplateImportFilePaths(final Map<String, Object> mappedToscaTemplate, final Path fileParentDir) {
108         final Either<Object, ResultStatusEnum> importsEither =
109             findToscaElement(mappedToscaTemplate, ToscaTagNamesEnum.IMPORTS, ToscaElementTypeEnum.ALL);
110
111         if (importsEither.isLeft()) {
112             final List importsList = (List) importsEither.left().value();
113             if (CollectionUtils.isNotEmpty(importsList)) {
114                 if (importsList.get(0) instanceof String) {
115                     List<Path> importPaths = new ArrayList<>();
116                     importsList.forEach(
117                         importPath -> {
118                             final Path path = fileParentDir == null ?
119                                 Paths.get((String) importPath) : fileParentDir.resolve(Paths.get((String) importPath)).normalize();
120                             importPaths.add(path);
121                         });
122                     return importPaths;
123                 } else if (importsList.get(0) instanceof Map) {
124                     return getTemplateImportFilePathsMultiLineGrammar(importsList, fileParentDir);
125                 }
126             }
127
128         }
129         return Collections.emptyList();
130     }
131
132     @SuppressWarnings("unchecked")
133     private List<Path> getTemplateImportFilePathsMultiLineGrammar(final List<Map<String, Object>> importsList, final Path fileParentDir) {
134         final List<Path> importFiles = new ArrayList<>();
135
136         for (Map<String, Object> importFileMultiLineGrammar : importsList) {
137             if (MapUtils.isNotEmpty(importFileMultiLineGrammar)) {
138                 if (importFileMultiLineGrammar.values().iterator().next() instanceof String) {
139                     Path relativePath = Paths.get((String) importFileMultiLineGrammar.get("file"));
140                     Path absolutePath = fileParentDir == null ? relativePath : fileParentDir.resolve(relativePath).normalize();
141                     importFiles.add(absolutePath);
142                 } else if (importFileMultiLineGrammar.values().iterator().next() instanceof Map) {
143                     importFileMultiLineGrammar.values().forEach(value -> {
144                         Path relativePath = Paths.get((String) ((Map<String, Object>) value).get("file"));
145                         Path absolutePath = fileParentDir == null ? relativePath : fileParentDir.resolve(relativePath).normalize();
146                         importFiles.add(absolutePath);
147                     });
148                 }
149             }
150         }
151         return importFiles;
152     }
153
154     @Override
155     public Map<String, NodeTypeInfo> extractTypesInfo() {
156         return Collections.emptyMap();
157     }
158
159     @Override
160     public Map<String, Object> getDataTypes() {
161         return getTypes(ToscaTagNamesEnum.DATA_TYPES);
162     }
163
164     @Override
165     public Map<String, Object> getGroupTypes() {
166         return getTypes(ToscaTagNamesEnum.GROUP_TYPES);
167     }
168
169     @Override
170     public Map<String, Object> getCapabilityTypes() {
171         return getTypes(ToscaTagNamesEnum.CAPABILITY_TYPES);
172     }
173
174     @Override
175     public Map<String, Object> getArtifactTypes() {
176         return getTypes(ToscaTagNamesEnum.ARTIFACT_TYPES);
177     }
178
179     @Override
180     public Map<String, Object> getInterfaceTypes() {
181         return getTypes(ToscaTagNamesEnum.INTERFACE_TYPES);
182     }
183
184     private Map<String, Object> getTypes(ToscaTagNamesEnum toscaTag) {
185         final Map<String, Object> types = new HashMap<>();
186         mainTemplateImports.entrySet().forEach(entry -> types.putAll(getTypesFromTemplate(entry.getValue(), toscaTag)));
187         types.putAll(getTypesFromTemplate(getMappedToscaMainTemplate(), toscaTag));
188         return types;
189     }
190
191     public List<NodeTypeDefinition> getNodeTypesUsed() {
192         if (nodeTypeDefinitions == null) {
193             nodeTypeDefinitions = new ArrayList<>();
194             final Set<String> nodeTypesUsed = getNodeTypesUsedInToscaTemplate(getMappedToscaMainTemplate());
195             nodeTypeDefinitions.addAll(getNodeTypeDefinitions(nodeTypesUsed).values());
196         }
197         nodeTypeDefinitions = sortNodeTypesByDependencyOrder(nodeTypeDefinitions);
198         return nodeTypeDefinitions;
199     }
200
201     private List<NodeTypeDefinition> sortNodeTypesByDependencyOrder(final List<NodeTypeDefinition> nodeTypes) {
202         final List<NodeTypeDefinition> sortedNodeTypeDefinitions = new ArrayList<>();
203         final Map<String, NodeTypeDefinition> nodeTypeDefinitionsMap = new HashMap<>();
204
205         nodeTypes.forEach(nodeType -> {
206             int highestDependencyIndex = -1;
207             for (final String dependencyName : getDependencyTypes(nodeType, nodeTypes)) {
208                 final NodeTypeDefinition dependency = nodeTypeDefinitionsMap.get(dependencyName);
209                 final int indexOfDependency = sortedNodeTypeDefinitions.lastIndexOf(dependency);
210                 highestDependencyIndex = Math.max(indexOfDependency, highestDependencyIndex);
211             }
212             sortedNodeTypeDefinitions.add(highestDependencyIndex + 1, nodeType);
213             nodeTypeDefinitionsMap.put(nodeType.getMappedNodeType().getKey(), nodeType);
214         });
215         return sortedNodeTypeDefinitions;
216     }
217
218     private Collection<String> getDependencyTypes(final NodeTypeDefinition nodeType, final List<NodeTypeDefinition> nodeTypes) {
219         final Set<String> dependencies = new HashSet<>();
220         Either<Object, ResultStatusEnum> derivedFromTypeEither = findToscaElement((Map<String, Object>) nodeType.getMappedNodeType().getValue(),
221             TypeUtils.ToscaTagNamesEnum.DERIVED_FROM, ToscaElementTypeEnum.STRING);
222         if (derivedFromTypeEither.isLeft() && derivedFromTypeEither.left().value() != null) {
223             final String derivedFrom = (String) derivedFromTypeEither.left().value();
224             dependencies.add(derivedFrom);
225             nodeTypes.stream().filter(derivedFromCandidate -> derivedFrom.contentEquals(derivedFromCandidate.getMappedNodeType().getKey()))
226                 .forEach(derivedFromNodeType -> dependencies.addAll(getDependencyTypes(derivedFromNodeType, nodeTypes)));
227         }
228         return dependencies;
229     }
230
231     private Map<String, NodeTypeDefinition> getNodeTypeDefinitions(final Set<String> nodeTypesToGet) {
232         final Map<String, NodeTypeDefinition> foundNodeTypes = getTypes(nodeTypesToGet);
233         final Map<String, NodeTypeDefinition> nodeTypesToReturn = new HashMap<>(foundNodeTypes);
234         final Set<String> recursiveNodeTypesToGet = new HashSet<>();
235         foundNodeTypes.values().forEach(nodeTypeDef -> {
236             Either<Object, ResultStatusEnum> derivedFromTypeEither =
237                 findToscaElement((Map<String, Object>) nodeTypeDef.getMappedNodeType().getValue(), TypeUtils.ToscaTagNamesEnum.DERIVED_FROM,
238                     ToscaElementTypeEnum.STRING);
239             if (derivedFromTypeEither.isLeft()) {
240                 recursiveNodeTypesToGet.add((String) derivedFromTypeEither.left().value());
241             }
242         });
243         recursiveNodeTypesToGet.removeAll(nodeTypesToGet);
244         if (CollectionUtils.isNotEmpty(recursiveNodeTypesToGet)) {
245             nodeTypesToReturn.putAll(getNodeTypeDefinitions(recursiveNodeTypesToGet));
246         }
247         return nodeTypesToReturn;
248     }
249
250     private Map<String, NodeTypeDefinition> getTypes(final Set<String> nodeTypes) {
251         final Map<String, NodeTypeDefinition> nodeTypeDefinitionsMap = new HashMap<>();
252         final Set<String> lowerPrecedenceImports = new HashSet<>();
253
254         if (model != null && !model.equals(Constants.DEFAULT_MODEL_NAME)) {
255             final Set<String> modelImports = new HashSet<>();
256             modelOperation.findAllModelImports(model, true).forEach(modelImport -> modelImports.add("Definitions/" + modelImport.getFullPath()));
257
258             lowerPrecedenceImports.add("Definitions/" + ModelOperation.ADDITIONAL_TYPE_DEFINITIONS_PATH);
259             lowerPrecedenceImports.addAll(modelImports);
260
261             mainTemplateImports.entrySet().stream().filter(entry -> modelImports.contains(entry.getKey()))
262                     .forEach(template -> addTypesFromTemplate(nodeTypeDefinitionsMap, template.getValue(), nodeTypes));
263
264             mainTemplateImports.entrySet().stream().filter(entry -> entry.getKey().equals(ModelOperation.ADDITIONAL_TYPE_DEFINITIONS_PATH.toString()))
265                     .forEach(template -> addTypesFromTemplate(nodeTypeDefinitionsMap, template.getValue(), nodeTypes));
266         }
267
268         mainTemplateImports.entrySet().stream().filter(entry -> !lowerPrecedenceImports.contains(entry.getKey()))
269                 .forEach(template -> addTypesFromTemplate(nodeTypeDefinitionsMap, template.getValue(), nodeTypes));
270
271         return nodeTypeDefinitionsMap;
272     }
273
274     private void addTypesFromTemplate(final Map<String, NodeTypeDefinition> nodeTypeDefinitionsMap, final Map<String, Object> mappedTemplate,
275             final Set<String> nodeTypes) {
276         final Map<String, Object> types = getTypesFromTemplate(mappedTemplate, ToscaTagNamesEnum.NODE_TYPES, nodeTypes);
277         if (MapUtils.isNotEmpty(types)) {
278             types.entrySet().forEach(typesEntry -> {
279                 final NodeTypeMetadata metadata = getMetaDataFromTemplate(mappedTemplate, typesEntry.getKey());
280                 nodeTypeDefinitionsMap.put(typesEntry.getKey(), new NodeTypeDefinition(typesEntry, metadata));
281             });
282         }
283     }
284
285     @SuppressWarnings("unchecked")
286     private Set<String> getNodeTypesUsedInToscaTemplate(final Map<String, Object> mappedToscaTemplate) {
287         final var nodeTemplatesEither = findToscaElement(mappedToscaTemplate, TypeUtils.ToscaTagNamesEnum.NODE_TEMPLATES, ToscaElementTypeEnum.MAP);
288         final Set<String> nodeTypesUsedInToscaTemplate = new HashSet<>();
289         if (nodeTemplatesEither.isLeft()) {
290             final var nodeTemplates = (Map<String, Map<String, Object>>) nodeTemplatesEither.left().value();
291             nodeTypesUsedInToscaTemplate.addAll(findNodeTypesUsedInNodeTemplates(nodeTemplates));
292         }
293         final var substitutionMappingsNodeType = findFirstToscaStringElement(mappedToscaTemplate, ToscaTagNamesEnum.NODE_TYPE);
294         if (substitutionMappingsNodeType.isLeft()){
295             nodeTypesUsedInToscaTemplate.add(substitutionMappingsNodeType.left().value());
296         }
297         return nodeTypesUsedInToscaTemplate;
298     }
299
300     private NodeTypeMetadata getMetaDataFromTemplate(Map<String, Object> mappedResourceTemplate, String nodeTemplateType) {
301         NodeTypeMetadata nodeTypeMetadata = new NodeTypeMetadata();
302         Either<Map<String, Object>, ImportUtils.ResultStatusEnum> metadataEither = ImportUtils.findFirstToscaMapElement(mappedResourceTemplate,
303             TypeUtils.ToscaTagNamesEnum.METADATA);
304         if (metadataEither.isLeft() && metadataEither.left().value().get("type").equals(ResourceTypeEnum.VFC.getValue())) {
305             Map<String, Object> metadata = metadataEither.left().value();
306             createMetadataFromTemplate(nodeTypeMetadata, metadata, nodeTemplateType);
307         } else {
308             nodeTypeMetadata = createDefaultMetadata(nodeTemplateType);
309         }
310         return nodeTypeMetadata;
311     }
312
313     private void createMetadataFromTemplate(NodeTypeMetadata nodeTypeMetadata, Map<String, Object> metadata, String nodeTemplateType) {
314         nodeTypeMetadata.setToscaName(nodeTemplateType);
315         nodeTypeMetadata.setContactId(getModifier().getUserId());
316         nodeTypeMetadata.setDescription((String) metadata.get("description"));
317         List<String> tags = new ArrayList<>();
318         tags.add((String) metadata.get("name"));
319         nodeTypeMetadata.setTags(tags);
320         SubCategoryDefinition subCategory = new SubCategoryDefinition();
321         subCategory.setName((String) metadata.get("subcategory"));
322         CategoryDefinition category = new CategoryDefinition();
323         category.setName((String) metadata.get("category"));
324         category.setNormalizedName(((String) metadata.get("category")).toLowerCase());
325         category.setIcons(List.of(DEFAULT_ICON));
326         category.addSubCategory(subCategory);
327         List<CategoryDefinition> categories = new ArrayList<>();
328         categories.add(category);
329         nodeTypeMetadata.setCategories(categories);
330         nodeTypeMetadata.setName((String) metadata.get("name"));
331         nodeTypeMetadata.setIcon("defaulticon");
332         nodeTypeMetadata.setResourceVendorModelNumber((String) metadata.get("resourceVendorModelNumber"));
333         nodeTypeMetadata.setResourceType((String) metadata.get("type"));
334         nodeTypeMetadata.setVendorName((String) metadata.get("resourceVendor"));
335         nodeTypeMetadata.setVendorRelease(String.valueOf(metadata.get("resourceVendorRelease")));
336         nodeTypeMetadata.setModel(model);
337         nodeTypeMetadata.setNormative(false);
338     }
339
340     private NullNodeTypeMetadata createDefaultMetadata(String nodeTemplateType) {
341         NullNodeTypeMetadata nodeTypeMetadata = new NullNodeTypeMetadata();
342         nodeTypeMetadata.setToscaName(nodeTemplateType);
343         nodeTypeMetadata.setModel(model);
344         return nodeTypeMetadata;
345     }
346 }