372517c5f90268581b7455f5b5e9f80b8550e367
[sdc.git] /
1 /*
2  * Copyright © 2016-2017 European Support Limited
3  * Modification Copyright (C) 2019 Nordix Foundation.
4  * Modification Copyright (C) 2021 Nokia.
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  *
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.
17  */
18
19 package org.openecomp.sdc.tosca.csar;
20
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableMap;
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Optional;
35 import org.apache.commons.collections.MapUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
38 import org.openecomp.sdc.common.errors.Messages;
39 import org.openecomp.sdc.logging.api.Logger;
40 import org.openecomp.sdc.logging.api.LoggerFactory;
41
42 abstract class AbstractOnboardingManifest implements Manifest {
43
44     protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractOnboardingManifest.class);
45     protected static final int MAX_ALLOWED_MANIFEST_META_ENTRIES = 4;
46     protected Map<String, String> metadata;
47     protected List<String> sources;
48     protected Map<String, List<String>> nonManoSources;
49     protected Map<String, AlgorithmDigest> sourceAndChecksumMap = new HashMap<>();
50     protected Map<String, SignatureData> sourceAndSignatureMap = new HashMap<>();
51     protected String cmsSignature;
52     protected List<String> errors;
53     protected boolean continueToProcess;
54     protected String currentLine;
55     protected Iterator<String> linesIterator;
56     protected int currentLineNumber;
57
58     protected AbstractOnboardingManifest() {
59         errors = new ArrayList<>();
60         sources = new ArrayList<>();
61         metadata = new HashMap<>();
62         nonManoSources = new HashMap<>();
63     }
64
65     @Override
66     public Optional<ResourceTypeEnum> getType() {
67         if (!isValid()) {
68             return Optional.empty();
69         }
70         final String firstKey = metadata.keySet().iterator().next();
71         final ManifestTokenType manifestTokenType = ManifestTokenType.parse(firstKey).orElse(null);
72         if (manifestTokenType == null) {
73             return Optional.empty();
74         }
75         if (manifestTokenType.isMetadataPnfEntry()) {
76             return Optional.of(ResourceTypeEnum.PNF);
77         }
78         return Optional.of(ResourceTypeEnum.VF);
79     }
80
81     @Override
82     public void parse(final InputStream manifestAsStream) {
83         try {
84             final ImmutableList<String> lines = readAllLines(manifestAsStream);
85             continueToProcess = true;
86             currentLineNumber = 0;
87             processManifest(lines);
88         } catch (IOException e) {
89             LOGGER.error(e.getMessage(), e);
90             errors.add(Messages.MANIFEST_PARSER_INTERNAL.getErrorMessage());
91         }
92     }
93
94     /**
95      * Process the manifest lines, reporting an error when detected.
96      *
97      * @param lines the manifest lines
98      */
99     protected void processManifest(final ImmutableList<String> lines) {
100         if (isEmptyManifest(lines)) {
101             return;
102         }
103         linesIterator = lines.iterator();
104         readNextNonEmptyLine();
105         if (!getCurrentLine().isPresent()) {
106             errors.add(Messages.MANIFEST_EMPTY.getErrorMessage());
107             return;
108         }
109
110         processMetadata();
111         processBody();
112     }
113
114     /**
115      * Process the metadata part of the Manifest file.
116      */
117     protected abstract void processMetadata();
118
119     /**
120      * Process the other parts from manifest different than metadata.
121      */
122     protected abstract void processBody();
123
124     /**
125      * Read the manifest as a list of lines.
126      *
127      * @param manifestAsStream The manifest file input stream
128      * @return The manifest as a list of string
129      * @throws IOException when the input stream is null or a read problem happened.
130      */
131     protected ImmutableList<String> readAllLines(final InputStream manifestAsStream) throws IOException {
132         if (manifestAsStream == null) {
133             throw new IOException("Manifest Input Stream cannot be null.");
134         }
135         final ImmutableList.Builder<String> builder = ImmutableList.builder();
136         try (final BufferedReader bufferedReader = new BufferedReader(
137             new InputStreamReader(manifestAsStream, StandardCharsets.UTF_8.newDecoder()))) {
138             bufferedReader.lines().forEach(builder::add);
139         }
140         return builder.build();
141     }
142
143     /**
144      * Checks if the line is a {@link ManifestTokenType#METADATA} entry.
145      *
146      * @param line The line to check
147      * @return {@code true} if the line is a 'metadata' entry, {@code false} otherwise.
148      */
149     protected boolean isMetadata(final String line) {
150         return line.trim()
151             .equals(ManifestTokenType.METADATA.getToken() + ManifestTokenType.ATTRIBUTE_VALUE_SEPARATOR.getToken());
152     }
153
154     /**
155      * Checks if the the entry is a valid metadata entry.
156      *
157      * @param metadataEntry the entry to be evaluated
158      * @return {@code true} if the entry is a valid metadata entry, {@code false} otherwise.
159      */
160     protected boolean isMetadataEntry(final String metadataEntry) {
161         final Optional<ManifestTokenType> manifestTokenType = ManifestTokenType.parse(metadataEntry);
162         return manifestTokenType.map(ManifestTokenType::isMetadataEntry).orElse(false);
163     }
164
165     /**
166      * Checks if the manifest is empty
167      *
168      * @param lines the manifest parsed as a string lines list
169      * @return {@code true} if the manifest is empty, {@code false} otherwise.
170      */
171     protected boolean isEmptyManifest(final ImmutableList<String> lines) {
172         if (lines == null || lines.isEmpty()) {
173             errors.add(Messages.MANIFEST_EMPTY.getErrorMessage());
174             return true;
175         }
176         return false;
177     }
178
179     /**
180      * Reports a manifest invalid line error occurred in the current line.
181      */
182     protected void reportInvalidLine() {
183         reportInvalidLine(currentLineNumber, getCurrentLine().orElse(""));
184     }
185
186     /**
187      * Reports a manifest invalid line error.
188      *
189      * @param lineNumber the line number
190      * @param line the line
191      */
192     protected void reportInvalidLine(final int lineNumber, final String line) {
193         errors.add(Messages.MANIFEST_INVALID_LINE.formatMessage(lineNumber, line));
194     }
195
196     /**
197      * Reports a manifest error occurred in the current line.
198      *
199      * @param message The error message
200      * @param params The message params
201      */
202     protected void reportError(final Messages message, final Object... params) {
203         reportError(currentLineNumber, getCurrentLine().orElse(""), message, params);
204     }
205
206     /**
207      * Reports a manifest error occurred in the specified line.
208      *
209      * @param lineNumber The line number
210      * @param line The line
211      * @param message The error message
212      * @param params The message params
213      */
214     protected void reportError(final int lineNumber, final String line, final Messages message,
215                                final Object... params) {
216         errors.add(Messages.MANIFEST_ERROR_WITH_LINE.formatMessage(message.formatMessage(params), lineNumber, line));
217     }
218
219     /**
220      * Checks if the manifest is valid.
221      *
222      * @return {@code true} if the manifest is valid, {@code false} otherwise.
223      */
224     public boolean isValid() {
225         return errors.isEmpty();
226     }
227
228     /**
229      * Reads the next non empty line in the manifest. Updates the current line and line number.
230      *
231      * @return the next non empty line. If there is no more lines, an empty value.
232      */
233     protected Optional<String> readNextNonEmptyLine() {
234         while (linesIterator.hasNext()) {
235             final String line = linesIterator.next().trim();
236             currentLineNumber++;
237             if (!line.isEmpty()) {
238                 currentLine = line;
239                 return getCurrentLine();
240             }
241             currentLine = null;
242         }
243
244         if (getCurrentLine().isPresent()) {
245             currentLineNumber++;
246             currentLine = null;
247         }
248
249         return getCurrentLine();
250     }
251
252     /**
253      * Gets the current line.
254      *
255      * @return the current line.
256      */
257     protected Optional<String> getCurrentLine() {
258         return Optional.ofNullable(currentLine);
259     }
260
261     /**
262      * Reads the current line entry name. The entry name and value must be separated by {@link
263      * ManifestTokenType#ATTRIBUTE_VALUE_SEPARATOR}.
264      *
265      * @return the entry value
266      */
267     protected Optional<String> readCurrentEntryName() {
268         final Optional<String> line = getCurrentLine();
269         if (line.isPresent()) {
270             return readEntryName(line.get());
271         }
272
273         return Optional.empty();
274     }
275
276     /**
277      * Read a entry name. The entry name and value must be separated by {@link ManifestTokenType#ATTRIBUTE_VALUE_SEPARATOR}.
278      *
279      * @param line the entry line
280      * @return returns the entry name
281      */
282     protected Optional<String> readEntryName(final String line) {
283         if (StringUtils.isEmpty(line)) {
284             return Optional.empty();
285         }
286         if (!line.contains(ManifestTokenType.ATTRIBUTE_VALUE_SEPARATOR.getToken())) {
287             return Optional.empty();
288         }
289         final String attribute = line.substring(0, line.indexOf(ManifestTokenType.ATTRIBUTE_VALUE_SEPARATOR.getToken())).trim();
290         if (StringUtils.isEmpty(attribute)) {
291             return Optional.empty();
292         }
293
294         return Optional.of(attribute);
295     }
296
297     /**
298      * Reads the current line entry value. The entry name and value must be separated by {@link
299      * ManifestTokenType#ATTRIBUTE_VALUE_SEPARATOR}.
300      *
301      * @return the entry value
302      */
303     protected Optional<String> readCurrentEntryValue() {
304         final Optional<String> line = getCurrentLine();
305         if (line.isPresent()) {
306             return readEntryValue(line.get());
307         }
308
309         return Optional.empty();
310     }
311
312     /**
313      * Reads a entry value. The entry name and value must be separated by {@link ManifestTokenType#ATTRIBUTE_VALUE_SEPARATOR}.
314      *
315      * @param line the entry line
316      * @return the entry value
317      */
318     protected Optional<String> readEntryValue(final String line) {
319         if (StringUtils.isEmpty(line)) {
320             return Optional.empty();
321         }
322         if (!line.contains(ManifestTokenType.ATTRIBUTE_VALUE_SEPARATOR.getToken())) {
323             return Optional.empty();
324         }
325         final String value = line.substring(line.indexOf(ManifestTokenType.ATTRIBUTE_VALUE_SEPARATOR.getToken()) + 1).trim();
326         if (StringUtils.isEmpty(value)) {
327             return Optional.empty();
328         }
329
330         return Optional.of(value);
331     }
332
333     /**
334      * Adds a entry to the metadata map. Only accepts new entries. If the entry is duplicated a manifest error is
335      * reported.
336      *
337      * @param entry the metadata entry
338      * @param value the entry value
339      * @return {@code true} if the entry was added, {@code false} otherwise.
340      */
341     protected boolean addToMetadata(final String entry, final String value) {
342         if (metadata.containsKey(entry)) {
343             reportError(Messages.MANIFEST_METADATA_DUPLICATED_ENTRY, entry);
344             return false;
345         }
346
347         metadata.put(entry, value);
348         return true;
349     }
350
351     public List<String> getErrors() {
352         return ImmutableList.copyOf(errors);
353     }
354
355     public Map<String, String> getMetadata() {
356         if (!isValid()) {
357             return Collections.emptyMap();
358         }
359         return ImmutableMap.copyOf(metadata);
360     }
361
362     public List<String> getSources() {
363         if (!isValid()) {
364             return Collections.emptyList();
365         }
366         return ImmutableList.copyOf(sources);
367     }
368
369     public Map<String, List<String>> getNonManoSources() {
370         if (!isValid()) {
371             return Collections.emptyMap();
372         }
373         return ImmutableMap.copyOf(nonManoSources);
374     }
375
376     @Override
377     public boolean isSigned() {
378         return getCmsSignature().isPresent();
379     }
380
381     @Override
382     public Optional<String> getCmsSignature() {
383         return Optional.ofNullable(cmsSignature);
384     }
385
386     @Override
387     public Optional<Map<String, AlgorithmDigest>> getSourceAndChecksumMap() {
388         if (MapUtils.isEmpty(sourceAndChecksumMap)) {
389             return Optional.empty();
390         }
391
392         return Optional.of(ImmutableMap.copyOf(sourceAndChecksumMap));
393     }
394 }