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
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.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
20 package org.openecomp.core.impl;
22 import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES;
25 import java.util.ArrayList;
26 import java.util.LinkedHashSet;
27 import java.util.List;
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;
39 * Handles TOSCA definition imports, checking for import definition errors.
41 public class ToscaDefinitionImportHandler {
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;
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
53 public ToscaDefinitionImportHandler(final Map<String, byte[]> fileStructureMap, final String mainDefinitionFilePath) {
54 this.fileMap = fileStructureMap;
55 handleImports(mainDefinitionFilePath);
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.
63 * @param fileName the descriptor file path
65 private void handleImports(final String fileName) {
66 currentFile = fileName;
67 if (!checkImportExists(fileName)) {
70 final ServiceTemplateReaderService readerService;
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()));
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);
90 * Iterates reads each import statement in the given list.
92 * example of a descriptor.yaml import statement
94 * - /Artifacts/anImportedDescriptor.yaml
95 * - anotherDescriptor: anotherImportedDescriptor.yaml
96 * - yetAnotherDescriptor:
97 * yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
99 * @param imports the import statements
101 * The list of import file paths found
103 private List<String> extractFileImports(final List<Object> imports) {
104 final List<String> importedFileList = new ArrayList<>();
105 imports.forEach(importObject -> importedFileList.addAll(readImportStatement(importObject)));
107 return importedFileList;
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.
116 * example of yaml imports statements:
117 * - /Artifacts/anImportedDescriptor.yaml
118 * - anotherDescriptor: anotherImportedDescriptor.yaml
119 * - yetAnotherDescriptor:
120 * yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
122 * @param importObject the object representing the yaml import statement
124 * The list of import file paths found
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()));
138 reportError(ErrorLevel.ERROR,
139 String.format(Messages.INVALID_IMPORT_STATEMENT.getErrorMessage(), currentFile, importObject));
142 return importedFileList;
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
150 * The resolved path of the import, using as base the directory path
152 private String resolveImportPath(final String directoryPath, final String importPath) {
153 final String fixedParentDir;
154 if (StringUtils.isEmpty(directoryPath)) {
155 fixedParentDir = "/";
157 fixedParentDir = String.format("%s%s%s",
158 directoryPath.startsWith("/") ? "" : "/"
160 , directoryPath.endsWith("/") ? "" : "/");
163 final URI parentDirUri = URI.create(fixedParentDir);
165 String resolvedImportPath = parentDirUri.resolve(importPath).toString();
166 if (resolvedImportPath.contains("../")) {
167 reportError(ErrorLevel.ERROR,
168 Messages.INVALID_IMPORT_STATEMENT.formatMessage(currentFile, importPath));
171 if (resolvedImportPath.startsWith("/")) {
172 resolvedImportPath = resolvedImportPath.substring(1);
175 return resolvedImportPath;
179 * Checks if the given file path exists inside the file structure.
180 * Reports an error if the file was not found.
182 * @param filePath file path to check inside the file structure
184 * {@code true} if the file exists, {@code false} otherwise
186 private boolean checkImportExists(final String filePath) {
187 if (!fileMap.keySet().contains(filePath)) {
188 reportError(ErrorLevel.ERROR, Messages.MISSING_IMPORT_FILE.formatMessage(filePath));
196 * Gets all processed files during the import handling.
198 * A list containing the processed files paths
200 public Set<String> getHandledDefinitionFilesList() {
201 return handledDefinitionFilesList;
205 * Adds an error to the validation error list.
207 * @param errorLevel the error level
208 * @param errorMessage the error message
210 private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
211 validationErrorList.add(new ErrorMessage(errorLevel, errorMessage));
215 * Gets the list of errors.
217 * The import validation errors detected
219 public List<ErrorMessage> getErrors() {
220 return validationErrorList;
224 * Checks if the handler detected a import error.
226 * {@code true} if the handler detected any error, {@code false} otherwise.
228 public boolean hasError() {
229 return !validationErrorList.isEmpty();