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