37b86db3cf12e07ea4d094b5fd376d288b923572
[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.ValidValuesConstraint;
46 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
47 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
48 import org.openecomp.sdc.exception.ResponseFormat;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class PropertyValueConstraintValidationUtil {
53
54     private static final String UNDERSCORE = "_";
55     private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
56     private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
57     private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
58     private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
59     private final ObjectMapper objectMapper = new ObjectMapper();
60     private final List<String> errorMessages = new ArrayList<>();
61     private StringBuilder completePropertyName;
62     private String completeInputName;
63
64     public Either<Boolean, ResponseFormat> validatePropertyConstraints(final Collection<? extends PropertyDefinition> propertyDefinitionList,
65                                                                        final ApplicationDataTypeCache applicationDataTypeCache,
66                                                                        final String model) {
67
68         dataTypeDefinitionCache = applicationDataTypeCache.getAll(model).left().value();
69         CollectionUtils.emptyIfNull(propertyDefinitionList).stream()
70             .filter(this::isValuePresent)
71             .forEach(this::evaluatePropertyTypeForConstraintValidation);
72         if (CollectionUtils.isNotEmpty(errorMessages)) {
73             final String errorMsgAsString = String.join(",", errorMessages);
74             logger.debug("Properties with Invalid Data: {}", errorMsgAsString);
75             return Either.right(getResponseFormatManager().getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, errorMsgAsString));
76         }
77         return Either.left(Boolean.TRUE);
78     }
79
80     private boolean isValuePresent(PropertyDefinition propertyDefinition) {
81         if (propertyDefinition instanceof ComponentInstanceInput) {
82             return StringUtils.isNotEmpty(propertyDefinition.getValue());
83         }
84         if (propertyDefinition instanceof InputDefinition) {
85             return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
86         }
87         return StringUtils.isNotEmpty(propertyDefinition.getValue());
88     }
89
90     private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
91         if (propertyDefinition == null || propertyDefinition.getType() == null || !dataTypeDefinitionCache.containsKey(propertyDefinition.getType())) {
92             errorMessages.add("\nUnsupported datatype found for property " + getCompletePropertyName(propertyDefinition));
93             return;
94         }
95         completeInputName = "";
96         completePropertyName = new StringBuilder();
97         if (propertyDefinition instanceof ComponentInstanceInput) {
98             setCompletePropertyName(propertyDefinition);
99             evaluateComplexTypeProperties(propertyDefinition);
100             return;
101         }
102         if (propertyDefinition instanceof InputDefinition) {
103             completeInputName = propertyDefinition.getName();
104             propertyDefinition = getPropertyDefinitionObjectFromInputs(propertyDefinition);
105         }
106         if (propertyDefinition != null) {
107             List<PropertyConstraint> propertyConstraints =
108                 dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints();
109             if (ToscaType.isPrimitiveType(propertyDefinition.getType())) {
110                 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
111                     propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
112                 evaluateConstraintsOnProperty(propertyDefinition);
113             } else if (ToscaType.isCollectionType(propertyDefinition.getType())) {
114                 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
115                     propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
116                 evaluateConstraintsOnProperty(propertyDefinition);
117                 evaluateCollectionTypeProperties(propertyDefinition);
118             } else {
119                 setCompletePropertyName(propertyDefinition);
120                 evaluateComplexTypeProperties(propertyDefinition);
121             }
122         }
123     }
124
125     private void setCompletePropertyName(PropertyDefinition propertyDefinition) {
126         if (StringUtils.isNotBlank(propertyDefinition.getUniqueId())) {
127             completePropertyName.append(propertyDefinition.getUniqueId().substring(propertyDefinition.getUniqueId().lastIndexOf('.') + 1));
128         }
129     }
130
131     private void evaluateConstraintsOnProperty(PropertyDefinition propertyDefinition) {
132         ToscaType toscaType = ToscaType.isValidType(propertyDefinition.getType());
133         if (isPropertyNotMappedAsInput(propertyDefinition) && CollectionUtils.isNotEmpty(propertyDefinition.getConstraints())) {
134             for (PropertyConstraint propertyConstraint : propertyDefinition.getConstraints()) {
135                 try {
136                     propertyConstraint.initialize(toscaType);
137                     propertyConstraint.validate(toscaType, propertyDefinition.getValue());
138                 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
139                     errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaType, exception, getCompletePropertyName(propertyDefinition)));
140                 }
141             }
142         } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
143             .isValidValue(propertyDefinition.getValue())) {
144             errorMessages.add(String
145                 .format("\nUnsupported value provided for %s property supported value " + "type is %s.", getCompletePropertyName(propertyDefinition),
146                     toscaType.getType()));
147         }
148     }
149
150     private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
151         return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
152     }
153
154     private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
155         if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
156             PropertyDefinition definition = new PropertyDefinition();
157             definition.setValue(propertyDefinition.getValue());
158             definition.setType(dataTypeDefinition.getName());
159             definition.setConstraints(dataTypeDefinition.getConstraints());
160             evaluateConstraintsOnProperty(propertyDefinition);
161         }
162     }
163
164     private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
165         List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
166         try {
167             Map<String, Object> valueMap = MapUtils
168                 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
169                 }));
170             if (CollectionUtils.isEmpty(propertyDefinitions)) {
171                 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
172             } else {
173                 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
174             }
175         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
176             logger.debug(e.getMessage(), e);
177             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
178         }
179     }
180
181     private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
182         try {
183             if (valueMap.containsKey(prop.getName())) {
184                 if (ToscaType.isPrimitiveType(prop.getType())) {
185                     evaluateConstraintsOnProperty(copyPropertyWithNewValue(prop, String.valueOf(valueMap.get(prop.getName()))));
186                 } else if (ToscaType.isCollectionType(prop.getType())) {
187                     evaluateCollectionTypeProperties(copyPropertyWithNewValue(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
188                 } else {
189                     completePropertyName.append(UNDERSCORE);
190                     completePropertyName.append(prop.getName());
191                     evaluateComplexTypeProperties(copyPropertyWithNewValue(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
192                 }
193             }
194         } catch (IOException e) {
195             logger.error(e.getMessage(), e);
196             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
197         }
198     }
199
200     private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
201         ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
202         if (ToscaType.LIST == toscaPropertyType) {
203             evaluateListType(propertyDefinition);
204         } else if (ToscaType.MAP == toscaPropertyType) {
205             evaluateMapType(propertyDefinition);
206         }
207     }
208
209     private void evaluateListType(PropertyDefinition propertyDefinition) {
210         try {
211             if (propertyDefinition.getSchemaType() == null) {
212                 propertyDefinition.setSchema(createStringSchema());
213             }
214             List<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
215             evaluateCollectionType(propertyDefinition, list);
216         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
217             logger.debug(e.getMessage(), e);
218             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
219         }
220     }
221
222     private SchemaDefinition createStringSchema() {
223         final SchemaDefinition schemaDefinition = new SchemaDefinition();
224         final PropertyDefinition schemaStringProperty = new PropertyDefinition();
225         schemaStringProperty.setType(ToscaType.STRING.getType());
226         schemaDefinition.setProperty(schemaStringProperty);
227         return schemaDefinition;
228     }
229
230     private void evaluateMapType(final PropertyDefinition propertyDefinition) {
231         try {
232             if (propertyDefinition.getSchemaType() == null) {
233                 propertyDefinition.setSchema(createStringSchema());
234             }
235             final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
236             evaluateCollectionType(propertyDefinition, map.values());
237         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
238             logger.debug(e.getMessage(), e);
239             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
240         }
241     }
242
243     private void evaluateCollectionPrimitiveSchemaType(final PropertyDefinition propertyDefinition,
244                                                        final String schemaType) throws JsonProcessingException {
245         if (propertyDefinition.getSchema() != null && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
246             propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
247             propertyDefinition.setValue(objectMapper.readValue(propertyDefinition.getValue(), String.class));
248             propertyDefinition.setType(schemaType);
249             evaluateConstraintsOnProperty(propertyDefinition);
250         }
251     }
252
253     private void evaluateCollectionType(final PropertyDefinition propertyDefinition, final Collection<Object> valueList) {
254         final String schemaType = propertyDefinition.getSchemaType();
255         for (final Object value : valueList) {
256             try {
257                 final PropertyDefinition propertyCopyWithNewValue = copyPropertyWithNewValue(propertyDefinition, objectMapper.writeValueAsString(value));
258                 if (ToscaType.isPrimitiveType(schemaType)) {
259                     evaluateCollectionPrimitiveSchemaType(propertyCopyWithNewValue, schemaType);
260                 } else if (ToscaType.isCollectionType(schemaType)) {
261                     propertyCopyWithNewValue.setType(schemaType);
262                     propertyCopyWithNewValue.setSchemaType(propertyDefinition.getSchemaProperty().getSchemaType());
263                     evaluateCollectionTypeProperties(propertyCopyWithNewValue);
264                 } else {
265                     propertyCopyWithNewValue.setType(schemaType);
266                     completePropertyName.append(UNDERSCORE);
267                     completePropertyName.append(propertyCopyWithNewValue.getName());
268                     evaluateComplexTypeProperties(propertyCopyWithNewValue);
269                 }
270             } catch (final Exception e) {
271                 logger.debug(e.getMessage(), e);
272                 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
273             }
274         }
275     }
276
277     private String getCompletePropertyName(final PropertyDefinition propertyDefinition) {
278         if (StringUtils.isNotBlank(completeInputName)) {
279             return completeInputName;
280         }
281
282         final String propertyName = propertyDefinition == null ? "" : propertyDefinition.getName();
283         if (StringUtils.isNotBlank(completePropertyName)) {
284             return completePropertyName + UNDERSCORE + propertyName;
285         }
286
287         return propertyName;
288     }
289
290     private PropertyDefinition copyPropertyWithNewValue(final PropertyDefinition propertyToCopy, final String value) {
291         final var propertyDefinition = new PropertyDefinition(propertyToCopy);
292         propertyDefinition.setValue(value);
293         return propertyDefinition;
294     }
295
296     private boolean isValidValueConstraintPresent(List<PropertyConstraint> propertyConstraints) {
297         return propertyConstraints != null && propertyConstraints.stream().anyMatch(ValidValuesConstraint.class::isInstance);
298     }
299
300     private PropertyDefinition getPropertyDefinitionObjectFromInputs(PropertyDefinition property) {
301         InputDefinition inputDefinition = (InputDefinition) property;
302         PropertyDefinition propertyDefinition = null;
303         if (CollectionUtils.isEmpty(inputDefinition.getProperties()) || ToscaType.isPrimitiveType(inputDefinition.getProperties().get(0).getType())) {
304             propertyDefinition = new PropertyDefinition();
305             propertyDefinition.setType(inputDefinition.getType());
306             propertyDefinition.setValue(inputDefinition.getDefaultValue());
307             propertyDefinition.setName(inputDefinition.getName());
308         } else if (Objects.nonNull(inputDefinition.getInputPath())) {
309             propertyDefinition = evaluateComplexTypeInputs(inputDefinition);
310         }
311         return propertyDefinition;
312     }
313
314     private PropertyDefinition evaluateComplexTypeInputs(InputDefinition inputDefinition) {
315         Map<String, Object> inputMap = new HashMap<>();
316         PropertyDefinition propertyDefinition = new PropertyDefinition();
317         String[] inputPathArr = inputDefinition.getInputPath().split("#");
318         if (inputPathArr.length > 1) {
319             inputPathArr = ArrayUtils.remove(inputPathArr, 0);
320         }
321         try {
322             Map<String, Object> presentMap = inputMap;
323             for (int i = 0; i < inputPathArr.length; i++) {
324                 if (i == inputPathArr.length - 1) {
325                     presentMap.computeIfAbsent(inputPathArr[i], k -> inputDefinition.getDefaultValue());
326                 } else {
327                     presentMap.computeIfAbsent(inputPathArr[i], k -> new HashMap<String, Object>());
328                     presentMap = (Map<String, Object>) presentMap.get(inputPathArr[i]);
329                 }
330             }
331             if (CollectionUtils.isNotEmpty(inputDefinition.getProperties())) {
332                 propertyDefinition.setType(inputDefinition.getProperties().get(0).getType());
333             }
334             propertyDefinition.setName(inputDefinition.getName());
335             propertyDefinition.setValue(objectMapper.writeValueAsString(inputMap));
336         } catch (IOException e) {
337             logger.error(e.getMessage(), e);
338             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, inputDefinition.getName()));
339         }
340         return propertyDefinition;
341     }
342
343     protected ResponseFormatManager getResponseFormatManager() {
344         return ResponseFormatManager.getInstance();
345     }
346 }