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