8422c89f2e8dac7b81119a2acd719d1ffb32c245
[sdc.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
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  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
19
20 package org.openecomp.core.impl;
21
22 import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES;
23
24 import java.net.URI;
25 import java.util.ArrayList;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import org.apache.commons.io.FilenameUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.openecomp.core.converter.ServiceTemplateReaderService;
33 import org.openecomp.core.impl.services.ServiceTemplateReaderServiceImpl;
34 import org.openecomp.sdc.common.errors.Messages;
35 import org.openecomp.sdc.datatypes.error.ErrorLevel;
36 import org.openecomp.sdc.datatypes.error.ErrorMessage;
37
38 /**
39  * Handles TOSCA definition imports, checking for import definition errors.
40  */
41 public class ToscaDefinitionImportHandler {
42
43     private final Map<String, byte[]> fileMap;
44     private final Set<String> handledDefinitionFilesList = new LinkedHashSet<>();
45     private final List<ErrorMessage> validationErrorList = new ArrayList<>();
46     private String currentFile;
47
48     /**
49      * Reads the provided package structure starting from a main definition yaml file.
50      * @param fileStructureMap      The package structure with file path and respective file byte
51      * @param mainDefinitionFilePath    The main descriptor yaml file to start the reading
52      */
53     public ToscaDefinitionImportHandler(final Map<String, byte[]> fileStructureMap, final String mainDefinitionFilePath) {
54         this.fileMap = fileStructureMap;
55         handleImports(mainDefinitionFilePath);
56     }
57
58     /**
59      * Reads and validates the descriptor imports recursively.
60      * Starts from the provided descriptor and goes until the end of the import tree.
61      * Processes each file just once.
62      *
63      * @param fileName      the descriptor file path
64      */
65     private void handleImports(final String fileName) {
66         currentFile = fileName;
67         if (!checkImportExists(fileName)) {
68             return;
69         }
70         final ServiceTemplateReaderService readerService;
71         try {
72             readerService = new ServiceTemplateReaderServiceImpl(fileMap.get(fileName));
73         } catch (final Exception ex) {
74             reportError(ErrorLevel.ERROR,
75                 String.format(Messages.INVALID_YAML_FORMAT.getErrorMessage(), ex.getMessage()));
76             return;
77         }
78         handledDefinitionFilesList.add(fileName);
79         final List<Object> imports = readerService.getImports();
80         final List<String> extractImportFiles = extractFileImports(imports);
81         for (final String importedFile : extractImportFiles) {
82             final String resolvedPath = resolveImportPath(FilenameUtils.getPath(fileName), importedFile);
83             if (!handledDefinitionFilesList.contains(resolvedPath)) {
84                 handleImports(resolvedPath);
85             }
86         }
87     }
88
89     /**
90      * Iterates reads each import statement in the given list.
91      * <pre>
92      * example of a descriptor.yaml import statement
93      * imports:
94      * - /Artifacts/anImportedDescriptor.yaml
95      * - anotherDescriptor: anotherImportedDescriptor.yaml
96      * - yetAnotherDescriptor:
97      *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
98      * </pre>
99      * @param imports   the import statements
100      * @return
101      *  The list of import file paths found
102      */
103     private List<String> extractFileImports(final List<Object> imports) {
104         final List<String> importedFileList = new ArrayList<>();
105         imports.forEach(importObject -> importedFileList.addAll(readImportStatement(importObject)));
106
107         return importedFileList;
108     }
109
110     /**
111      * Reads an import statement which can be a value, a [key:value] or a [key:[key:value]].
112      * Ignores entries which contains the same keys as
113      * {@link org.openecomp.sdc.tosca.csar.CSARConstants#NON_FILE_IMPORT_ATTRIBUTES}.
114      * Reports invalid import statements.
115      * <pre>
116      * example of yaml imports statements:
117      * - /Artifacts/anImportedDescriptor.yaml
118      * - anotherDescriptor: anotherImportedDescriptor.yaml
119      * - yetAnotherDescriptor:
120      *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
121      * </pre>
122      * @param importObject      the object representing the yaml import statement
123      * @return
124      *  The list of import file paths found
125      */
126     private List<String> readImportStatement(final Object importObject) {
127         final List<String> importedFileList = new ArrayList<>();
128         if (importObject instanceof String) {
129             importedFileList.add((String) importObject);
130         } else if (importObject instanceof Map) {
131             final Map<String, Object> importObjectMap = (Map) importObject;
132             for (final Map.Entry entry : importObjectMap.entrySet()) {
133                 if (NON_FILE_IMPORT_ATTRIBUTES.stream().noneMatch(attr -> entry.getKey().equals(attr))) {
134                     importedFileList.addAll(readImportStatement(entry.getValue()));
135                 }
136             }
137         } else {
138             reportError(ErrorLevel.ERROR,
139                 String.format(Messages.INVALID_IMPORT_STATEMENT.getErrorMessage(), currentFile, importObject));
140         }
141
142         return importedFileList;
143     }
144
145     /**
146      * Given a directory path, resolves the import path.
147      * @param directoryPath     A directory path to resolve the import path
148      * @param importPath        An import statement path
149      * @return
150      *  The resolved path of the import, using as base the directory path
151      */
152     private String resolveImportPath(final String directoryPath, final String importPath) {
153         final String fixedParentDir;
154         if (StringUtils.isEmpty(directoryPath)) {
155             fixedParentDir = "/";
156         } else {
157             fixedParentDir = String.format("%s%s%s",
158                 directoryPath.startsWith("/") ? "" : "/"
159                 , directoryPath
160                 , directoryPath.endsWith("/") ? "" : "/");
161         }
162
163         final URI parentDirUri = URI.create(fixedParentDir);
164
165         String resolvedImportPath = parentDirUri.resolve(importPath).toString();
166         if (resolvedImportPath.contains("../")) {
167             reportError(ErrorLevel.ERROR,
168                 Messages.INVALID_IMPORT_STATEMENT.formatMessage(currentFile, importPath));
169             return null;
170         }
171         if (resolvedImportPath.startsWith("/")) {
172             resolvedImportPath = resolvedImportPath.substring(1);
173         }
174
175         return resolvedImportPath;
176     }
177
178     /**
179      * Checks if the given file path exists inside the file structure.
180      * Reports an error if the file was not found.
181      *
182      * @param filePath  file path to check inside the file structure
183      * @return
184      *  {@code true} if the file exists, {@code false} otherwise
185      */
186     private boolean checkImportExists(final String filePath) {
187         if (!fileMap.keySet().contains(filePath)) {
188             reportError(ErrorLevel.ERROR, Messages.MISSING_IMPORT_FILE.formatMessage(filePath));
189             return false;
190         }
191
192         return true;
193     }
194
195     /**
196      * Gets all processed files during the import handling.
197      * @return
198      *  A list containing the processed files paths
199      */
200     public Set<String> getHandledDefinitionFilesList() {
201         return handledDefinitionFilesList;
202     }
203
204     /**
205      * Adds an error to the validation error list.
206      *
207      * @param errorLevel        the error level
208      * @param errorMessage      the error message
209      */
210     private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
211         validationErrorList.add(new ErrorMessage(errorLevel, errorMessage));
212     }
213
214     /**
215      * Gets the list of errors.
216      * @return
217      *  The import validation errors detected
218      */
219     public List<ErrorMessage> getErrors() {
220         return validationErrorList;
221     }
222
223     /**
224      * Checks if the handler detected a import error.
225      * @return
226      *  {@code true} if the handler detected any error, {@code false} otherwise.
227      */
228     public boolean hasError() {
229         return !validationErrorList.isEmpty();
230     }
231 }