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