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