/* * ============LICENSE_START======================================================= * Copyright (C) 2019 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ package org.openecomp.core.impl; import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES; import java.net.URI; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.openecomp.core.converter.ServiceTemplateReaderService; import org.openecomp.core.impl.services.ServiceTemplateReaderServiceImpl; import org.openecomp.sdc.common.errors.Messages; import org.openecomp.sdc.datatypes.error.ErrorLevel; import org.openecomp.sdc.datatypes.error.ErrorMessage; /** * Handles TOSCA definition imports, checking for import definition errors. */ public class ToscaDefinitionImportHandler { private final Map fileMap; private final Set handledDefinitionFilesList = new LinkedHashSet<>(); private final List validationErrorList = new ArrayList<>(); private String currentFile; /** * Reads the provided package structure starting from a main definition yaml file. * @param fileStructureMap The package structure with file path and respective file byte * @param mainDefinitionFilePath The main descriptor yaml file to start the reading */ public ToscaDefinitionImportHandler(final Map fileStructureMap, final String mainDefinitionFilePath) { this.fileMap = fileStructureMap; handleImports(mainDefinitionFilePath); } /** * Reads and validates the descriptor imports recursively. * Starts from the provided descriptor and goes until the end of the import tree. * Processes each file just once. * * @param fileName the descriptor file path */ private void handleImports(final String fileName) { currentFile = fileName; if (!checkImportExists(fileName)) { return; } final ServiceTemplateReaderService readerService; try { readerService = new ServiceTemplateReaderServiceImpl(fileMap.get(fileName)); } catch (final Exception ex) { reportError(ErrorLevel.ERROR, String.format(Messages.INVALID_YAML_FORMAT.getErrorMessage(), ex.getMessage())); return; } handledDefinitionFilesList.add(fileName); final List imports = readerService.getImports(); final List extractImportFiles = extractFileImports(imports); for (final String importedFile : extractImportFiles) { final String resolvedPath = resolveImportPath(FilenameUtils.getPath(fileName), importedFile); if (!handledDefinitionFilesList.contains(resolvedPath)) { handleImports(resolvedPath); } } } /** * Iterates reads each import statement in the given list. *
     * example of a descriptor.yaml import statement
     * imports:
     * - /Artifacts/anImportedDescriptor.yaml
     * - anotherDescriptor: anotherImportedDescriptor.yaml
     * - yetAnotherDescriptor:
     *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
     * 
* @param imports the import statements * @return * The list of import file paths found */ private List extractFileImports(final List imports) { final List importedFileList = new ArrayList<>(); imports.forEach(importObject -> importedFileList.addAll(readImportStatement(importObject))); return importedFileList; } /** * Reads an import statement which can be a value, a [key:value] or a [key:[key:value]]. * Ignores entries which contains the same keys as * {@link org.openecomp.sdc.tosca.csar.CSARConstants#NON_FILE_IMPORT_ATTRIBUTES}. * Reports invalid import statements. *
     * example of yaml imports statements:
     * - /Artifacts/anImportedDescriptor.yaml
     * - anotherDescriptor: anotherImportedDescriptor.yaml
     * - yetAnotherDescriptor:
     *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
     * 
* @param importObject the object representing the yaml import statement * @return * The list of import file paths found */ private List readImportStatement(final Object importObject) { final List importedFileList = new ArrayList<>(); if (importObject instanceof String) { importedFileList.add((String) importObject); } else if (importObject instanceof Map) { final Map importObjectMap = (Map) importObject; for (final Map.Entry entry : importObjectMap.entrySet()) { if (NON_FILE_IMPORT_ATTRIBUTES.stream().noneMatch(attr -> entry.getKey().equals(attr))) { importedFileList.addAll(readImportStatement(entry.getValue())); } } } else { reportError(ErrorLevel.ERROR, String.format(Messages.INVALID_IMPORT_STATEMENT.getErrorMessage(), currentFile, importObject)); } return importedFileList; } /** * Given a directory path, resolves the import path. * @param directoryPath A directory path to resolve the import path * @param importPath An import statement path * @return * The resolved path of the import, using as base the directory path */ private String resolveImportPath(final String directoryPath, final String importPath) { final String fixedParentDir; if (StringUtils.isEmpty(directoryPath)) { fixedParentDir = "/"; } else { fixedParentDir = String.format("%s%s%s", directoryPath.startsWith("/") ? "" : "/" , directoryPath , directoryPath.endsWith("/") ? "" : "/"); } final URI parentDirUri = URI.create(fixedParentDir); String resolvedImportPath = parentDirUri.resolve(importPath).toString(); if (resolvedImportPath.contains("../")) { reportError(ErrorLevel.ERROR, Messages.INVALID_IMPORT_STATEMENT.formatMessage(currentFile, importPath)); return null; } if (resolvedImportPath.startsWith("/")) { resolvedImportPath = resolvedImportPath.substring(1); } return resolvedImportPath; } /** * Checks if the given file path exists inside the file structure. * Reports an error if the file was not found. * * @param filePath file path to check inside the file structure * @return * {@code true} if the file exists, {@code false} otherwise */ private boolean checkImportExists(final String filePath) { if (!fileMap.keySet().contains(filePath)) { reportError(ErrorLevel.ERROR, Messages.MISSING_IMPORT_FILE.formatMessage(filePath)); return false; } return true; } /** * Gets all processed files during the import handling. * @return * A list containing the processed files paths */ public Set getHandledDefinitionFilesList() { return handledDefinitionFilesList; } /** * Adds an error to the validation error list. * * @param errorLevel the error level * @param errorMessage the error message */ private void reportError(final ErrorLevel errorLevel, final String errorMessage) { validationErrorList.add(new ErrorMessage(errorLevel, errorMessage)); } /** * Gets the list of errors. * @return * The import validation errors detected */ public List getErrors() { return validationErrorList; } /** * Checks if the handler detected a import error. * @return * {@code true} if the handler detected any error, {@code false} otherwise. */ public boolean hasError() { return !validationErrorList.isEmpty(); } }