95692470477a0cdd25b3ce0c4e16e4dc06ba9dc5
[sdc.git] /
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.type.TypeReference;
19 import com.fasterxml.jackson.databind.ObjectMapper;
20 import fj.data.Either;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import org.apache.commons.collections4.CollectionUtils;
29 import org.apache.commons.collections4.ListUtils;
30 import org.apache.commons.collections4.MapUtils;
31 import org.apache.commons.lang3.ArrayUtils;
32 import org.apache.commons.lang3.StringUtils;
33 import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
34 import org.openecomp.sdc.be.dao.api.ActionStatus;
35 import org.openecomp.sdc.be.model.DataTypeDefinition;
36 import org.openecomp.sdc.be.model.InputDefinition;
37 import org.openecomp.sdc.be.model.PropertyConstraint;
38 import org.openecomp.sdc.be.model.PropertyDefinition;
39 import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache;
40 import org.openecomp.sdc.be.model.tosca.ToscaType;
41 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintUtil;
42 import org.openecomp.sdc.be.model.tosca.constraints.ValidValuesConstraint;
43 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
44 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
45 import org.openecomp.sdc.exception.ResponseFormat;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public class PropertyValueConstraintValidationUtil {
50
51     private static final String UNDERSCORE = "_";
52     private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
53     private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
54     private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
55     private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
56     private ObjectMapper objectMapper = new ObjectMapper();
57     private List<String> errorMessages = new ArrayList<>();
58     private StringBuilder completePropertyName;
59     private String completeInputName;
60
61     public static PropertyValueConstraintValidationUtil getInstance() {
62         return new PropertyValueConstraintValidationUtil();
63     }
64
65     public Either<Boolean, ResponseFormat> validatePropertyConstraints(Collection<? extends PropertyDefinition> propertyDefinitionList,
66                                                                        ApplicationDataTypeCache applicationDataTypeCache) {
67         ResponseFormatManager responseFormatManager = getResponseFormatManager();
68         dataTypeDefinitionCache = applicationDataTypeCache.getAll().left().value();
69         CollectionUtils.emptyIfNull(propertyDefinitionList).stream().filter(this::isValuePresent)
70             .forEach(this::evaluatePropertyTypeForConstraintValidation);
71         if (CollectionUtils.isNotEmpty(errorMessages)) {
72             logger.error("Properties with Invalid Data:", errorMessages);
73             ResponseFormat inputResponse = responseFormatManager
74                 .getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, String.join(",", errorMessages));
75             return Either.right(inputResponse);
76         }
77         return Either.left(Boolean.TRUE);
78     }
79
80     private boolean isValuePresent(PropertyDefinition propertyDefinition) {
81         if (propertyDefinition instanceof InputDefinition) {
82             return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
83         }
84         return StringUtils.isNotEmpty(propertyDefinition.getValue());
85     }
86
87     private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
88         if (Objects.nonNull(propertyDefinition.getType()) && dataTypeDefinitionCache.containsKey(propertyDefinition.getType())) {
89             completeInputName = "";
90             completePropertyName = new StringBuilder();
91             if (propertyDefinition instanceof InputDefinition) {
92                 completeInputName = propertyDefinition.getName();
93                 propertyDefinition = getPropertyDefinitionObjectFromInputs(propertyDefinition);
94             }
95             if (Objects.nonNull(propertyDefinition)) {
96                 if (ToscaType.isPrimitiveType(propertyDefinition.getType())) {
97                     propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
98                         dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints()));
99                     evaluateConstraintsOnProperty(propertyDefinition);
100                 } else if (ToscaType.isCollectionType(propertyDefinition.getType())) {
101                     propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
102                         dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints()));
103                     evaluateConstraintsOnProperty(propertyDefinition);
104                     evaluateCollectionTypeProperties(propertyDefinition);
105                 } else {
106                     setCompletePropertyName(propertyDefinition);
107                     evaluateComplexTypeProperties(propertyDefinition);
108                 }
109             }
110         } else {
111             errorMessages.add("\nUnsupported datatype found for property " + getCompletePropertyName(propertyDefinition));
112         }
113     }
114
115     private void setCompletePropertyName(PropertyDefinition propertyDefinition) {
116         if (StringUtils.isNotBlank(propertyDefinition.getUniqueId())) {
117             completePropertyName.append(propertyDefinition.getUniqueId().substring(propertyDefinition.getUniqueId().lastIndexOf('.') + 1));
118         }
119     }
120
121     private void evaluateConstraintsOnProperty(PropertyDefinition propertyDefinition) {
122         ToscaType toscaType = ToscaType.isValidType(propertyDefinition.getType());
123         if (isPropertyNotMappedAsInput(propertyDefinition) && CollectionUtils.isNotEmpty(propertyDefinition.getConstraints())
124             && isValidValueConstraintPresent(propertyDefinition.getConstraints())) {
125             for (PropertyConstraint propertyConstraint : propertyDefinition.getConstraints()) {
126                 try {
127                     propertyConstraint.initialize(toscaType);
128                     propertyConstraint.validate(toscaType, propertyDefinition.getValue());
129                 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
130                     errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaType, exception, getCompletePropertyName(propertyDefinition)));
131                 }
132             }
133         } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
134             .isValidValue(propertyDefinition.getValue())) {
135             errorMessages.add(String
136                 .format("\nUnsupported value provided for %s property supported value " + "type is %s.", getCompletePropertyName(propertyDefinition),
137                     toscaType.getType()));
138         }
139     }
140
141     private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
142         return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
143     }
144
145     private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
146         if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
147             PropertyDefinition definition = new PropertyDefinition();
148             definition.setValue(propertyDefinition.getValue());
149             definition.setType(dataTypeDefinition.getName());
150             definition.setConstraints(dataTypeDefinition.getConstraints());
151             evaluateConstraintsOnProperty(propertyDefinition);
152         }
153     }
154
155     private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
156         List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
157         try {
158             Map<String, Object> valueMap = MapUtils
159                 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<Map<String, Object>>() {
160                 }));
161             if (CollectionUtils.isEmpty(propertyDefinitions)) {
162                 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
163             } else {
164                 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
165             }
166         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
167             logger.debug(e.getMessage(), e);
168             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
169         }
170     }
171
172     private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
173         try {
174             if (valueMap.containsKey(prop.getName())) {
175                 if (ToscaType.isPrimitiveType(prop.getType())) {
176                     evaluateConstraintsOnProperty(createPropertyDefinition(prop, String.valueOf(valueMap.get(prop.getName()))));
177                 } else if (ToscaType.isCollectionType(prop.getType())) {
178                     evaluateCollectionTypeProperties(createPropertyDefinition(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
179                 } else {
180                     completePropertyName.append(UNDERSCORE);
181                     completePropertyName.append(prop.getName());
182                     evaluateComplexTypeProperties(createPropertyDefinition(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
183                 }
184             }
185         } catch (IOException e) {
186             logger.error(e.getMessage(), e);
187             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
188         }
189     }
190
191     private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
192         ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
193         if (ToscaType.LIST == toscaPropertyType) {
194             evaluateListType(propertyDefinition);
195         } else if (ToscaType.MAP == toscaPropertyType) {
196             evaluateMapType(propertyDefinition);
197         }
198     }
199
200     private void evaluateListType(PropertyDefinition propertyDefinition) {
201         try {
202             String schemaType = propertyDefinition.getSchemaType();
203             List list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<List<Object>>() {
204             });
205             evaluateCollectionType(propertyDefinition, list, schemaType);
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 void evaluateMapType(PropertyDefinition propertyDefinition) {
213         try {
214             String schemaType = propertyDefinition.getSchemaType();
215             Map map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<Map<String, Object>>() {
216             });
217             evaluateCollectionType(propertyDefinition, map.values(), schemaType);
218         } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
219             logger.debug(e.getMessage(), e);
220             errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
221         }
222     }
223
224     private void evaluateCollectionPrimitiveSchemaType(PropertyDefinition propertyDefinition, Object value, String schemaType) {
225         if (Objects.nonNull(propertyDefinition.getSchema()) && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
226             propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
227             propertyDefinition.setValue(String.valueOf(value));
228             propertyDefinition.setType(schemaType);
229             evaluateConstraintsOnProperty(propertyDefinition);
230         }
231     }
232
233     private void evaluateCollectionType(PropertyDefinition propertyDefinition, Collection valueList, String schemaType) {
234         for (Object value : valueList) {
235             try {
236                 if (ToscaType.isPrimitiveType(schemaType)) {
237                     evaluateCollectionPrimitiveSchemaType(propertyDefinition, value, schemaType);
238                 } else if (ToscaType.isCollectionType(schemaType)) {
239                     propertyDefinition.setValue(objectMapper.writeValueAsString(value));
240                     propertyDefinition.setType(schemaType);
241                     evaluateCollectionTypeProperties(propertyDefinition);
242                 } else {
243                     propertyDefinition.setValue(objectMapper.writeValueAsString(value));
244                     propertyDefinition.setType(schemaType);
245                     completePropertyName.append(UNDERSCORE);
246                     completePropertyName.append(propertyDefinition.getName());
247                     evaluateComplexTypeProperties(propertyDefinition);
248                 }
249             } catch (IOException e) {
250                 logger.debug(e.getMessage(), e);
251                 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
252             }
253         }
254     }
255
256     private String getCompletePropertyName(PropertyDefinition propertyDefinition) {
257         return StringUtils.isNotBlank(completeInputName) ? completeInputName
258             : StringUtils.isBlank(completePropertyName) ? propertyDefinition.getName()
259                 : completePropertyName + UNDERSCORE + propertyDefinition.getName();
260     }
261
262     private PropertyDefinition createPropertyDefinition(PropertyDefinition prop, String value) {
263         PropertyDefinition propertyDefinition = new PropertyDefinition();
264         propertyDefinition.setName(prop.getName());
265         propertyDefinition.setValue(value);
266         propertyDefinition.setType(prop.getType());
267         propertyDefinition.setConstraints(prop.getConstraints());
268         propertyDefinition.setSchema(prop.getSchema());
269         return propertyDefinition;
270     }
271
272     private boolean isValidValueConstraintPresent(List<PropertyConstraint> propertyConstraints) {
273         return propertyConstraints.stream().anyMatch(propertyConstraint -> propertyConstraint instanceof ValidValuesConstraint);
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 }