ZIP archive support for multiple YANG files delivery on Schema Set creation using...
[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  *  ================================================================================
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.onap.cps.rest.utils;
21
22 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION;
23
24 import com.google.common.collect.ImmutableMap;
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.nio.charset.StandardCharsets;
29 import java.util.Arrays;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipInputStream;
34 import lombok.AccessLevel;
35 import lombok.NoArgsConstructor;
36 import org.onap.cps.spi.exceptions.CpsException;
37 import org.onap.cps.spi.exceptions.ModelValidationException;
38 import org.springframework.web.multipart.MultipartFile;
39
40 @NoArgsConstructor(access = AccessLevel.PRIVATE)
41 public class MultipartFileUtil {
42
43     private static final String ZIP_FILE_EXTENSION = ".zip";
44     private static final String YANG_FILE_EXTENSION = RFC6020_YANG_FILE_EXTENSION;
45     private static final int READ_BUFFER_SIZE = 1024;
46
47     /**
48      * Extracts yang resources from multipart file instance.
49      *
50      * @param multipartFile the yang file uploaded
51      * @return yang resources as {map} where the key is original file name, and the value is file content
52      * @throws ModelValidationException if the file name extension is not '.yang' or '.zip'
53      *                                  or if zip archive contain no yang files
54      * @throws CpsException             if the file content cannot be read
55      */
56
57     public static Map<String, String> extractYangResourcesMap(final MultipartFile multipartFile) {
58         final String originalFileName = multipartFile.getOriginalFilename();
59         if (resourceNameEndsWithExtension(originalFileName, YANG_FILE_EXTENSION)) {
60             return ImmutableMap.of(originalFileName, extractYangResourceContent(multipartFile));
61         }
62         if (resourceNameEndsWithExtension(originalFileName, ZIP_FILE_EXTENSION)) {
63             return extractYangResourcesMapFromZipArchive(multipartFile);
64         }
65         throw new ModelValidationException("Unsupported file type.",
66             String.format("Filename %s matches none of expected extensions: %s", originalFileName,
67                 Arrays.asList(YANG_FILE_EXTENSION, ZIP_FILE_EXTENSION)));
68     }
69
70     private static Map<String, String> extractYangResourcesMapFromZipArchive(final MultipartFile multipartFile) {
71         final ImmutableMap.Builder<String, String> yangResourceMapBuilder = ImmutableMap.builder();
72
73         try (
74             final InputStream inputStream = multipartFile.getInputStream();
75             final ZipInputStream zipInputStream = new ZipInputStream(inputStream);
76         ) {
77             ZipEntry zipEntry;
78             while ((zipEntry = zipInputStream.getNextEntry()) != null) {
79                 extractZipEntryToMapIfApplicable(yangResourceMapBuilder, zipEntry, zipInputStream);
80             }
81             zipInputStream.closeEntry();
82
83         } catch (final IOException e) {
84             throw new CpsException("Cannot extract resources from zip archive.", e.getMessage(), e);
85         }
86
87         try {
88             final Map<String, String> yangResourceMap = yangResourceMapBuilder.build();
89             if (yangResourceMap.isEmpty()) {
90                 throw new ModelValidationException("Archive contains no YANG resources.",
91                     String.format("Archive contains no files having %s extension.", YANG_FILE_EXTENSION));
92             }
93             return yangResourceMap;
94
95         } catch (final IllegalArgumentException e) {
96             throw new ModelValidationException("Invalid ZIP archive content.",
97                 "Multiple resources with same name detected.", e);
98         }
99     }
100
101     private static void extractZipEntryToMapIfApplicable(
102         final ImmutableMap.Builder<String, String> yangResourceMapBuilder, final ZipEntry zipEntry,
103         final ZipInputStream zipInputStream) throws IOException {
104
105         final String yangResourceName = extractResourceNameFromPath(zipEntry.getName());
106         if (zipEntry.isDirectory() || !resourceNameEndsWithExtension(yangResourceName, YANG_FILE_EXTENSION)) {
107             return;
108         }
109         yangResourceMapBuilder.put(yangResourceName, extractYangResourceContent(zipInputStream));
110     }
111
112     private static boolean resourceNameEndsWithExtension(final String resourceName, final String extension) {
113         return resourceName != null && resourceName.toLowerCase(Locale.ENGLISH).endsWith(extension);
114     }
115
116     private static String extractResourceNameFromPath(final String path) {
117         return path == null ? "" : path.replaceAll("^.*[\\\\/]", "");
118     }
119
120     private static String extractYangResourceContent(final MultipartFile multipartFile) {
121         try {
122             return new String(multipartFile.getBytes(), StandardCharsets.UTF_8);
123         } catch (final IOException e) {
124             throw new CpsException("Cannot read the resource file.", e.getMessage(), e);
125         }
126     }
127
128     private static String extractYangResourceContent(final ZipInputStream zipInputStream) throws IOException {
129         try (final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
130             final byte[] buffer = new byte[READ_BUFFER_SIZE];
131             int numberOfBytesRead;
132             while ((numberOfBytesRead = zipInputStream.read(buffer, 0, READ_BUFFER_SIZE)) > 0) {
133                 byteArrayOutputStream.write(buffer, 0, numberOfBytesRead);
134             }
135             return byteArrayOutputStream.toString(StandardCharsets.UTF_8);
136         }
137     }
138 }