e3b0b2835c22ac358de0925f970b0ec31fff6415
[cps.git] / cps-rest / src / main / java / org / onap / cps / rest / utils / MultipartFileUtil.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2020 Pantheon.tech
4  *  Modifications Copyright (C) 2021 Bell Canada.
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  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.rest.utils;
22
23 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION;
24
25 import com.google.common.collect.ImmutableMap;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.nio.charset.StandardCharsets;
30 import java.util.Arrays;
31 import java.util.Locale;
32 import java.util.Map;
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;
40
41 @NoArgsConstructor(access = AccessLevel.PRIVATE)
42 public class MultipartFileUtil {
43
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;
47
48     /**
49      * Extracts yang resources from multipart file instance.
50      *
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
56      */
57
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 ImmutableMap.of(originalFileName, extractYangResourceContent(multipartFile));
62         }
63         if (resourceNameEndsWithExtension(originalFileName, ZIP_FILE_EXTENSION)) {
64             return extractYangResourcesMapFromZipArchive(multipartFile);
65         }
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)));
69     }
70
71     private static Map<String, String> extractYangResourcesMapFromZipArchive(final MultipartFile multipartFile) {
72         final ImmutableMap.Builder<String, String> yangResourceMapBuilder = ImmutableMap.builder();
73         final ZipFileSizeValidator zipFileSizeValidator = new ZipFileSizeValidator();
74         try (
75             final InputStream inputStream = multipartFile.getInputStream();
76             final ZipInputStream zipInputStream = new ZipInputStream(inputStream);
77         ) {
78             ZipEntry zipEntry;
79             while ((zipEntry = zipInputStream.getNextEntry()) != null) {
80                 extractZipEntryToMapIfApplicable(yangResourceMapBuilder, zipEntry, zipInputStream,
81                     zipFileSizeValidator);
82             }
83             zipInputStream.closeEntry();
84
85         } catch (final IOException e) {
86             throw new CpsException("Cannot extract resources from zip archive.", e.getMessage(), e);
87         }
88         zipFileSizeValidator.validateSizeAndEntries();
89         try {
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));
94             }
95             return yangResourceMap;
96
97         } catch (final IllegalArgumentException e) {
98             throw new ModelValidationException("Invalid ZIP archive content.",
99                 "Multiple resources with same name detected.", e);
100         }
101     }
102
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)) {
109             return;
110         }
111         yangResourceMapBuilder.put(yangResourceName, extractYangResourceContent(zipInputStream, zipFileSizeValidator));
112     }
113
114     private static boolean resourceNameEndsWithExtension(final String resourceName, final String extension) {
115         return resourceName != null && resourceName.toLowerCase(Locale.ENGLISH).endsWith(extension);
116     }
117
118     private static String extractResourceNameFromPath(final String path) {
119         return path == null ? "" : path.replaceAll("^.*[\\\\/]", "");
120     }
121
122     private static String extractYangResourceContent(final MultipartFile multipartFile) {
123         try {
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);
127         }
128     }
129
130     private static String extractYangResourceContent(final ZipInputStream zipInputStream,
131         final ZipFileSizeValidator zipFileSizeValidator) throws IOException {
132         try (final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
133             int totalSizeEntry = 0;
134             int numberOfBytesRead;
135             final byte[] buffer = new byte[READ_BUFFER_SIZE];
136             zipFileSizeValidator.incrementTotalEntryInArchive();
137             while ((numberOfBytesRead = zipInputStream.read(buffer, 0, READ_BUFFER_SIZE)) > 0) {
138                 byteArrayOutputStream.write(buffer, 0, numberOfBytesRead);
139                 totalSizeEntry += numberOfBytesRead;
140                 zipFileSizeValidator.updateTotalSizeArchive(totalSizeEntry);
141                 zipFileSizeValidator.validateCompresssionRatio(totalSizeEntry);
142             }
143             return byteArrayOutputStream.toString(StandardCharsets.UTF_8);
144         }
145     }
146 }