6aa59e484489a7ceb7ca80e0dee57820bcce7039
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / datamodel / utils / PropertyValueConstraintValidationUtil.java
1 /*
2  * Copyright © 2016-2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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 package org.openecomp.sdc.be.datamodel.utils;
17
18 import com.fasterxml.jackson.core.JsonProcessingException;
19 import com.fasterxml.jackson.core.type.TypeReference;
20 import com.fasterxml.jackson.databind.ObjectMapper;
21 import fj.data.Either;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import org.apache.commons.collections4.CollectionUtils;
30 import org.apache.commons.collections4.ListUtils;
31 import org.apache.commons.collections4.MapUtils;
32 import org.apache.commons.lang3.ArrayUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
35 import org.openecomp.sdc.be.dao.api.ActionStatus;
36 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
37 import org.openecomp.sdc.be.model.ComponentInstanceInput;
38 import org.openecomp.sdc.be.model.DataTypeDefinition;
39 import org.openecomp.sdc.be.model.InputDefinition;
40 import org.openecomp.sdc.be.model.PropertyConstraint;
41 import org.openecomp.sdc.be.model.PropertyDefinition;
42 import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache;
43 import org.openecomp.sdc.be.model.tosca.ToscaType;
44 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintUtil;
45 import org.openecomp.sdc.be.model.tosca.constraints.LengthConstraint;
46 import org.openecomp.sdc.be.model.tosca.constraints.MaxLengthConstraint;
47 import org.openecomp.sdc.be.model.tosca.constraints.MinLengthConstraint;
48 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
49 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
50 import org.openecomp.sdc.exception.ResponseFormat;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 public class PropertyValueConstraintValidationUtil {
55
56     private static final String UNDERSCORE = "_";
57     private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
58     private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
59     private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
60     private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
61     private final ObjectMapper objectMapper = new ObjectMapper();
62     private final List<String> errorMessages = new ArrayList<>();
63     private StringBuilder completePropertyName;
64     private String completeInputName;
65
66     public Either<Boolean, ResponseFormat> validatePropertyConstraints(final Collection<? extends PropertyDefinition> propertyDefinitionList,
67                                                                        final ApplicationDataTypeCache applicationDataTypeCache,
68                                                                        final String model) {
69
70         dataTypeDefinitionCache = applicationDataTypeCache.getAll(model).left().value();
71         CollectionUtils.emptyIfNull(propertyDefinitionList).stream()
72             .filter(this::isValuePresent)
73             .forEach(this::evaluatePropertyTypeForConstraintValidation);
74         if (CollectionUtils.isNotEmpty(errorMessages)) {
75             final String errorMsgAsString = String.join(",", errorMessages);
76             logger.debug("Properties with Invalid Data: {}", errorMsgAsString);
77             return Either.right(getResponseFormatManager().getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, errorMsgAsString));
78         }
79         return Either.left(Boolean.TRUE);
80     }
81
82     private boolean isValuePresent(PropertyDefinition propertyDefinition) {
83         if (propertyDefinition instanceof ComponentInstanceInput) {
84             return StringUtils.isNotEmpty(propertyDefinition.getValue());
85         }
86         if (propertyDefinition instanceof InputDefinition) {
87             return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
88         }
89         return StringUtils.isNotEmpty(propertyDefinition.getValue());
90     }
91
92     private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
93         if (propertyDefinition == null || propertyDefinition.getType() == null || !dataTypeDefinitionCache.containsKey(
94             propertyDefinition.getType())) {
95             errorMessages.add("\nUnsupported datatype found for property " + getCompletePropertyName(propertyDefinition));
96             return;
97         }
98         completeInputName = "";
99         completePropertyName = new StringBuilder();
100         if (propertyDefinition instanceof ComponentInstanceInput) {
101             setCompletePropertyName(propertyDefinition);
102             evaluateComplexTypeProperties(propertyDefinition);
103             return;
104         }
105         if (propertyDefinition instanceof InputDefinition) {
106             completeInputName = propertyDefinition.getName();
107             propertyDefinition = getPropertyDefinitionObjectFromInputs(propertyDefinition);
108         }
109         if (propertyDefinition != null) {
110             List<PropertyConstraint> propertyConstraints =
111                 dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints();
112             if (ToscaType.isPrimitiveType(propertyDefinition.getType())) {
113                 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
114                     propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
115                 evaluateConstraintsOnProperty(propertyDefinition);
116             } else if (ToscaType.isCollectionType(propertyDefinition.getType())) {
117                 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
118                     propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
119                 evaluateConstraintsOnProperty(propertyDefinition);
120                 evaluateCollectionTypeProperties(propertyDefinition);
121             } else {
122                 setCompletePropertyName(propertyDefinition);
123                 evaluateComplexTypeProperties(propertyDefinition);
124             }
125         }
126     }
127
128     private void setCompletePropertyName(PropertyDefinition propertyDefinition) {
129         if (StringUtils.isNotBlank(propertyDefinition.getUniqueId())) {
130             completePropertyName.append(propertyDefinition.getUniqueId().substring(propertyDefinition.getUniqueId().lastIndexOf('.') + 1));
131         }
132     }
133
134     private void evaluateConstraintsOnProperty(PropertyDefinition propertyDefinition) {
135         ToscaType toscaType = ToscaType.isValidType(propertyDefinition.getType());
136         if (isPropertyNotMappedAsInput(propertyDefinition) && CollectionUtils.isNotEmpty(propertyDefinition.getConstraints())) {
137             for (PropertyConstraint propertyConstraint : propertyDefinition.getConstraints()) {
138                 try {
139                     propertyConstraint.initialize(toscaType);
140                     propertyConstraint.validate(toscaType, propertyDefinition.getValue());
141                 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
142                     errorMessages.add(propertyConstraint.getErrorMessage(toscaType, exception, getCompletePropertyName(propertyDefinition)));
143                 } catch (IllegalArgumentException ie) {
144                     errorMessages.add(ie.getMessage());
145                 }
146             }
147         } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
148             .isValidValue(propertyDefinition.getValue())) {
149             errorMessages.add(String.format("Unsupported value provided for %s property supported value type is %s.",
150                 getCompletePropertyName(propertyDefinition), toscaType.getType()));
151         }
152     }
153
154     private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
155         return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
156     }
157
158     private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
159         if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
160             PropertyDefinition definition = new PropertyDefinition();
161             definition.setValue(propertyDefinition.getValue());
162             definition.setType(dataTypeDefinition.getName());
163             definition.setConstraints(dataTypeDefinition.getConstraints());
164             evaluateConstraintsOnProperty(propertyDefinition);
165         }
166     }
167
168     private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
169         List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
170         try {
171             Map<String, Object> valueMap = MapUtils
172                 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
173                 }));
174             if (CollectionUtils.isEmpty(propertyDefinitions)) {
175                 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
176             } else {
177                 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
178             }
179         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
180             logger.debug(e.getMessage(), e);
181             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
182         }
183     }
184
185     private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
186         try {
187             PropertyDefinition newPropertyWithValue;
188             if (valueMap.containsKey(prop.getName())) {
189                 if (ToscaType.isPrimitiveType(prop.getType())) {
190                     newPropertyWithValue = copyPropertyWithNewValue(prop, String.valueOf(valueMap.get(prop.getName())));
191                     if (isPropertyToEvaluate(newPropertyWithValue)) {
192                         evaluateConstraintsOnProperty(newPropertyWithValue);
193                     }
194                 } else if (ToscaType.isCollectionType(prop.getType())) {
195                     newPropertyWithValue =
196                         copyPropertyWithNewValue(prop,
197                             objectMapper.writeValueAsString(valueMap.get(prop.getName())));
198                     if (isPropertyToEvaluate(newPropertyWithValue)) {
199                         evaluateCollectionTypeProperties(newPropertyWithValue);
200                     }
201                 } else {
202                     newPropertyWithValue =
203                         copyPropertyWithNewValue(prop,
204                             objectMapper.writeValueAsString(valueMap.get(prop.getName())));
205                     if (isPropertyToEvaluate(newPropertyWithValue)) {
206                         evaluateComplexTypeProperties(newPropertyWithValue);
207                     }
208                 }
209             }
210         } catch (IOException | ConstraintValueDoNotMatchPropertyTypeException e) {
211             logger.error(e.getMessage(), e);
212             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
213         }
214     }
215
216     private boolean isPropertyToEvaluate(PropertyDefinition propertyDefinition) throws ConstraintValueDoNotMatchPropertyTypeException {
217         if (Boolean.FALSE.equals(propertyDefinition.isRequired())) {
218             if (!ToscaType.isCollectionType(propertyDefinition.getType())) {
219                 return StringUtils.isNotEmpty(propertyDefinition.getValue()) &&
220                     !"null".equals(propertyDefinition.getValue());
221             } else if (ToscaType.LIST == ToscaType.isValidType(propertyDefinition.getType())) {
222                 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
223                 });
224                 return CollectionUtils.isNotEmpty(list);
225             } else {
226                 Map<String, Object> valueMap = MapUtils
227                     .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
228                     }));
229                 return MapUtils.isNotEmpty(valueMap);
230             }
231         } else {
232             return true;
233         }
234     }
235
236     private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
237         ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
238         try {
239             if (isPropertyToEvaluate(propertyDefinition)) {
240                 evaluateCollectionConstraints(propertyDefinition, toscaPropertyType);
241             }
242         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
243             logger.error(e.getMessage(), e);
244             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
245         }
246         if (ToscaType.LIST == toscaPropertyType) {
247             evaluateListType(propertyDefinition);
248         } else if (ToscaType.MAP == toscaPropertyType) {
249             evaluateMapType(propertyDefinition);
250         }
251     }
252
253     private void evaluateCollectionConstraints(PropertyDefinition propertyDefinition, ToscaType toscaPropertyType) {
254         List<PropertyConstraint> constraintsList = propertyDefinition.getConstraints();
255
256         if (CollectionUtils.isEmpty(constraintsList)) {
257             return;
258         }
259         ToscaType toscaPropertyType1;
260         if (null == toscaPropertyType) {
261             toscaPropertyType1 = ToscaType.isValidType(propertyDefinition.getType());
262         } else {
263             toscaPropertyType1 = toscaPropertyType;
264         }
265         constraintsList.stream()
266             .filter(this::isACollectionConstraint)
267             .forEach(propertyConstraint -> {
268                 try {
269                     if (ToscaType.LIST == toscaPropertyType1) {
270                         Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
271                         });
272                         propertyConstraint.validate(list);
273                     } else if (ToscaType.MAP == toscaPropertyType1) {
274                         final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
275                         });
276                         propertyConstraint.validate(map);
277                     }
278                 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
279                     errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaPropertyType1, exception,
280                         getCompletePropertyName(propertyDefinition)));
281                 }
282             });
283     }
284
285     private boolean isACollectionConstraint(PropertyConstraint constraint) {
286         if (constraint instanceof MaxLengthConstraint) {
287             return true;
288         }
289         if (constraint instanceof MinLengthConstraint) {
290             return true;
291         }
292         return constraint instanceof LengthConstraint;
293     }
294
295     private void evaluateListType(PropertyDefinition propertyDefinition) {
296         try {
297             if (propertyDefinition.getSchemaType() == null) {
298                 propertyDefinition.setSchema(createStringSchema());
299             }
300             Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
301             });
302             evaluateCollectionType(propertyDefinition, list);
303         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
304             logger.debug(e.getMessage(), e);
305             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
306         }
307     }
308
309     private SchemaDefinition createStringSchema() {
310         final SchemaDefinition schemaDefinition = new SchemaDefinition();
311         final PropertyDefinition schemaStringProperty = new PropertyDefinition();
312         schemaStringProperty.setType(ToscaType.STRING.getType());
313         schemaDefinition.setProperty(schemaStringProperty);
314         return schemaDefinition;
315     }
316
317     private void evaluateMapType(final PropertyDefinition propertyDefinition) {
318         try {
319             if (propertyDefinition.getSchemaType() == null) {
320                 propertyDefinition.setSchema(createStringSchema());
321             }
322             final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
323             });
324             evaluateCollectionType(propertyDefinition, map.values());
325         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
326             logger.debug(e.getMessage(), e);
327             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
328         }
329     }
330
331     private void evaluateCollectionPrimitiveSchemaType(final PropertyDefinition propertyDefinition,
332                                                        final String schemaType) throws JsonProcessingException {
333         if (propertyDefinition.getSchema() != null && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
334             propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
335             propertyDefinition.setValue(objectMapper.readValue(propertyDefinition.getValue(), String.class));
336             propertyDefinition.setType(schemaType);
337             evaluateConstraintsOnProperty(propertyDefinition);
338         }
339     }
340
341     private void evaluateCollectionType(final PropertyDefinition propertyDefinition, final Collection<Object> valueList) {
342         final String schemaType = propertyDefinition.getSchemaType();
343         for (final Object value : valueList) {
344             try {
345                 final PropertyDefinition propertyCopyWithNewValue = copyPropertyWithNewValue(propertyDefinition,
346                     objectMapper.writeValueAsString(value));
347                 if (ToscaType.isPrimitiveType(schemaType)) {
348                     evaluateCollectionPrimitiveSchemaType(propertyCopyWithNewValue, schemaType);
349                 } else if (ToscaType.isCollectionType(schemaType)) {
350                     propertyCopyWithNewValue.setType(schemaType);
351                     propertyCopyWithNewValue.setSchemaType(propertyDefinition.getSchemaProperty().getSchemaType());
352                     evaluateCollectionTypeProperties(propertyCopyWithNewValue);
353                 } else {
354                     propertyCopyWithNewValue.setType(schemaType);
355                     completePropertyName.append(UNDERSCORE);
356                     completePropertyName.append(propertyCopyWithNewValue.getName());
357                     evaluateComplexTypeProperties(propertyCopyWithNewValue);
358                 }
359             } catch (final Exception e) {
360                 logger.debug(e.getMessage(), e);
361                 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
362             }
363         }
364     }
365
366     private String getCompletePropertyName(final PropertyDefinition propertyDefinition) {
367         if (StringUtils.isNotBlank(completeInputName)) {
368             return completeInputName;
369         }
370
371         final String propertyName = propertyDefinition == null ? "" : propertyDefinition.getName();
372         if (StringUtils.isNotBlank(completePropertyName)) {
373             return completePropertyName + UNDERSCORE + propertyName;
374         }
375
376         return propertyName;
377     }
378
379     private PropertyDefinition copyPropertyWithNewValue(final PropertyDefinition propertyToCopy, final String value) {
380         final var propertyDefinition = new PropertyDefinition(propertyToCopy);
381         propertyDefinition.setValue(value);
382         return propertyDefinition;
383     }
384
385     private PropertyDefinition getPropertyDefinitionObjectFromInputs(PropertyDefinition property) {
386         InputDefinition inputDefinition = (InputDefinition) property;
387         PropertyDefinition propertyDefinition = null;
388         if (CollectionUtils.isEmpty(inputDefinition.getProperties()) || ToscaType.isPrimitiveType(inputDefinition.getProperties().get(0).getType())) {
389             propertyDefinition = new PropertyDefinition();
390             propertyDefinition.setType(inputDefinition.getType());
391             propertyDefinition.setValue(inputDefinition.getDefaultValue());
392             propertyDefinition.setName(inputDefinition.getName());
393         } else if (Objects.nonNull(inputDefinition.getInputPath())) {
394             propertyDefinition = evaluateComplexTypeInputs(inputDefinition);
395         }
396         return propertyDefinition;
397     }
398
399     private PropertyDefinition evaluateComplexTypeInputs(InputDefinition inputDefinition) {
400         Map<String, Object> inputMap = new HashMap<>();
401         PropertyDefinition propertyDefinition = new PropertyDefinition();
402         String[] inputPathArr = inputDefinition.getInputPath().split("#");
403         if (inputPathArr.length > 1) {
404             inputPathArr = ArrayUtils.remove(inputPathArr, 0);
405         }
406         try {
407             Map<String, Object> presentMap = inputMap;
408             for (int i = 0; i < inputPathArr.length; i++) {
409                 if (i == inputPathArr.length - 1) {
410                     presentMap.computeIfAbsent(inputPathArr[i], k -> inputDefinition.getDefaultValue());
411                 } else {
412                     presentMap.computeIfAbsent(inputPathArr[i], k -> new HashMap<String, Object>());
413                     presentMap = (Map<String, Object>) presentMap.get(inputPathArr[i]);
414                 }
415             }
416             if (CollectionUtils.isNotEmpty(inputDefinition.getProperties())) {
417                 propertyDefinition.setType(inputDefinition.getProperties().get(0).getType());
418             }
419             propertyDefinition.setName(inputDefinition.getName());
420             propertyDefinition.setValue(objectMapper.writeValueAsString(inputMap));
421         } catch (IOException e) {
422             logger.error(e.getMessage(), e);
423             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, inputDefinition.getName()));
424         }
425         return propertyDefinition;
426     }
427
428     protected ResponseFormatManager getResponseFormatManager() {
429         return ResponseFormatManager.getInstance();
430     }
431 }