2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Pantheon.tech
4 * Modifications Copyright (C) 2021 Bell Canada.
5 * Modifications Copyright (C) 2023 Nordix Foundation.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.cps.rest.utils;
24 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION;
26 import com.google.common.collect.ImmutableMap;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.nio.charset.StandardCharsets;
30 import java.util.Arrays;
31 import java.util.Locale;
33 import java.util.zip.ZipEntry;
34 import java.util.zip.ZipInputStream;
35 import lombok.AccessLevel;
36 import lombok.NoArgsConstructor;
37 import org.onap.cps.spi.exceptions.CpsException;
38 import org.onap.cps.spi.exceptions.ModelValidationException;
39 import org.springframework.web.multipart.MultipartFile;
41 @NoArgsConstructor(access = AccessLevel.PRIVATE)
42 public class MultipartFileUtil {
44 private static final String ZIP_FILE_EXTENSION = ".zip";
45 private static final String YANG_FILE_EXTENSION = RFC6020_YANG_FILE_EXTENSION;
46 private static final int READ_BUFFER_SIZE = 1024;
49 * Extracts yang resources from multipart file instance.
51 * @param multipartFile the yang file uploaded
52 * @return yang resources as {map} where the key is original file name, and the value is file content
53 * @throws ModelValidationException if the file name extension is not '.yang' or '.zip'
54 * or if zip archive contain no yang files
55 * @throws CpsException if the file content cannot be read
58 public static Map<String, String> extractYangResourcesMap(final MultipartFile multipartFile) {
59 final String originalFileName = multipartFile.getOriginalFilename();
60 if (resourceNameEndsWithExtension(originalFileName, YANG_FILE_EXTENSION)) {
61 return Map.of(originalFileName, extractYangResourceContent(multipartFile));
63 if (resourceNameEndsWithExtension(originalFileName, ZIP_FILE_EXTENSION)) {
64 return extractYangResourcesMapFromZipArchive(multipartFile);
66 throw new ModelValidationException("Unsupported file type.",
67 String.format("Filename %s matches none of expected extensions: %s", originalFileName,
68 Arrays.asList(YANG_FILE_EXTENSION, ZIP_FILE_EXTENSION)));
71 private static Map<String, String> extractYangResourcesMapFromZipArchive(final MultipartFile multipartFile) {
72 final ImmutableMap.Builder<String, String> yangResourceMapBuilder = ImmutableMap.builder();
73 final var zipFileSizeValidator = new ZipFileSizeValidator();
75 final var inputStream = multipartFile.getInputStream();
76 final var zipInputStream = new ZipInputStream(inputStream);
79 while ((zipEntry = zipInputStream.getNextEntry()) != null) {
80 extractZipEntryToMapIfApplicable(yangResourceMapBuilder, zipEntry, zipInputStream,
81 zipFileSizeValidator);
83 zipInputStream.closeEntry();
85 } catch (final IOException e) {
86 throw new CpsException("Cannot extract resources from zip archive.", e.getMessage(), e);
88 zipFileSizeValidator.validateSizeAndEntries();
90 final Map<String, String> yangResourceMap = yangResourceMapBuilder.build();
91 if (yangResourceMap.isEmpty()) {
92 throw new ModelValidationException("Archive contains no YANG resources.",
93 String.format("Archive contains no files having %s extension.", YANG_FILE_EXTENSION));
95 return yangResourceMap;
97 } catch (final IllegalArgumentException e) {
98 throw new ModelValidationException("Invalid ZIP archive content.",
99 "Multiple resources with same name detected.", e);
103 private static void extractZipEntryToMapIfApplicable(
104 final ImmutableMap.Builder<String, String> yangResourceMapBuilder, final ZipEntry zipEntry,
105 final ZipInputStream zipInputStream, final ZipFileSizeValidator zipFileSizeValidator) throws IOException {
106 zipFileSizeValidator.setCompressedSize(zipEntry.getCompressedSize());
107 final String yangResourceName = extractResourceNameFromPath(zipEntry.getName());
108 if (zipEntry.isDirectory() || !resourceNameEndsWithExtension(yangResourceName, YANG_FILE_EXTENSION)) {
111 yangResourceMapBuilder.put(yangResourceName, extractYangResourceContent(zipInputStream, zipFileSizeValidator));
114 private static boolean resourceNameEndsWithExtension(final String resourceName, final String extension) {
115 return resourceName != null && resourceName.toLowerCase(Locale.ENGLISH).endsWith(extension);
118 private static String extractResourceNameFromPath(final String path) {
119 return path == null ? "" : path.replaceAll("^.*[\\\\/]", "");
122 private static String extractYangResourceContent(final MultipartFile multipartFile) {
124 return new String(multipartFile.getBytes(), StandardCharsets.UTF_8);
125 } catch (final IOException e) {
126 throw new CpsException("Cannot read the resource file.", e.getMessage(), e);
130 private static String extractYangResourceContent(final ZipInputStream zipInputStream,
131 final ZipFileSizeValidator zipFileSizeValidator)
133 try (final var byteArrayOutputStream = new ByteArrayOutputStream()) {
134 var totalSizeEntry = 0;
135 int numberOfBytesRead;
136 final var buffer = new byte[READ_BUFFER_SIZE];
137 zipFileSizeValidator.incrementTotalYangFileEntryCountInArchive();
138 while ((numberOfBytesRead = zipInputStream.read(buffer, 0, READ_BUFFER_SIZE)) > 0) {
139 byteArrayOutputStream.write(buffer, 0, numberOfBytesRead);
140 totalSizeEntry += numberOfBytesRead;
141 zipFileSizeValidator.updateTotalUncompressedSizeOfYangFilesInArchive(numberOfBytesRead);
142 zipFileSizeValidator.validateCompresssionRatio(totalSizeEntry);
144 return byteArrayOutputStream.toString(StandardCharsets.UTF_8);