2 * Copyright © 2016-2018 European Support Limited
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.openecomp.sdc.be.datamodel.utils;
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.Collections;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Objects;
30 import org.apache.commons.collections4.CollectionUtils;
31 import org.apache.commons.collections4.ListUtils;
32 import org.apache.commons.collections4.MapUtils;
33 import org.apache.commons.lang3.ArrayUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.openecomp.sdc.be.components.impl.ResponseFormatManager;
36 import org.openecomp.sdc.be.dao.api.ActionStatus;
37 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
38 import org.openecomp.sdc.be.model.ComponentInstanceInput;
39 import org.openecomp.sdc.be.model.DataTypeDefinition;
40 import org.openecomp.sdc.be.model.InputDefinition;
41 import org.openecomp.sdc.be.model.PropertyConstraint;
42 import org.openecomp.sdc.be.model.PropertyDefinition;
43 import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache;
44 import org.openecomp.sdc.be.model.tosca.ToscaType;
45 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintUtil;
46 import org.openecomp.sdc.be.model.tosca.constraints.LengthConstraint;
47 import org.openecomp.sdc.be.model.tosca.constraints.MaxLengthConstraint;
48 import org.openecomp.sdc.be.model.tosca.constraints.MinLengthConstraint;
49 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
50 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
51 import org.openecomp.sdc.exception.ResponseFormat;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
55 public class PropertyValueConstraintValidationUtil {
57 private static final String UNDERSCORE = "_";
58 private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
59 private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
60 private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
61 private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
62 private final ObjectMapper objectMapper = new ObjectMapper();
63 private final List<String> errorMessages = new ArrayList<>();
64 private StringBuilder completePropertyName;
65 private String completeInputName;
67 public Either<Boolean, ResponseFormat> validatePropertyConstraints(final Collection<? extends PropertyDefinition> propertyDefinitionList,
68 final ApplicationDataTypeCache applicationDataTypeCache,
71 dataTypeDefinitionCache = applicationDataTypeCache.getAll(model).left().value();
72 CollectionUtils.emptyIfNull(propertyDefinitionList).stream()
73 .filter(this::isValuePresent)
74 .forEach(this::evaluatePropertyTypeForConstraintValidation);
75 if (CollectionUtils.isNotEmpty(errorMessages)) {
76 final String errorMsgAsString = String.join(",", errorMessages);
77 logger.debug("Properties with Invalid Data: {}", errorMsgAsString);
78 return Either.right(getResponseFormatManager().getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, errorMsgAsString));
80 return Either.left(Boolean.TRUE);
83 private boolean isValuePresent(PropertyDefinition propertyDefinition) {
84 if (propertyDefinition instanceof ComponentInstanceInput) {
85 return StringUtils.isNotEmpty(propertyDefinition.getValue());
87 if (propertyDefinition instanceof InputDefinition) {
88 return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
90 return StringUtils.isNotEmpty(propertyDefinition.getValue());
93 private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
94 if (propertyDefinition == null || propertyDefinition.getType() == null || !dataTypeDefinitionCache.containsKey(propertyDefinition.getType())) {
95 errorMessages.add("\nUnsupported datatype found for property " + getCompletePropertyName(propertyDefinition));
98 completeInputName = "";
99 completePropertyName = new StringBuilder();
100 if (propertyDefinition instanceof ComponentInstanceInput) {
101 setCompletePropertyName(propertyDefinition);
102 evaluateComplexTypeProperties(propertyDefinition);
105 if (propertyDefinition instanceof InputDefinition) {
106 completeInputName = propertyDefinition.getName();
107 propertyDefinition = getPropertyDefinitionObjectFromInputs(propertyDefinition);
109 if (propertyDefinition != null) {
110 List<PropertyConstraint> propertyConstraints =
111 dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints();
112 if (ToscaType.isPrimitiveType(propertyDefinition.getType())) {
113 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
114 propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
115 evaluateConstraintsOnProperty(propertyDefinition);
116 } else if (ToscaType.isCollectionType(propertyDefinition.getType())) {
117 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
118 propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
119 evaluateConstraintsOnProperty(propertyDefinition);
120 evaluateCollectionTypeProperties(propertyDefinition);
122 setCompletePropertyName(propertyDefinition);
123 evaluateComplexTypeProperties(propertyDefinition);
128 private void setCompletePropertyName(PropertyDefinition propertyDefinition) {
129 if (StringUtils.isNotBlank(propertyDefinition.getUniqueId())) {
130 completePropertyName.append(propertyDefinition.getUniqueId().substring(propertyDefinition.getUniqueId().lastIndexOf('.') + 1));
134 private void evaluateConstraintsOnProperty(PropertyDefinition propertyDefinition) {
135 ToscaType toscaType = ToscaType.isValidType(propertyDefinition.getType());
136 if (isPropertyNotMappedAsInput(propertyDefinition) && CollectionUtils.isNotEmpty(propertyDefinition.getConstraints())) {
137 for (PropertyConstraint propertyConstraint : propertyDefinition.getConstraints()) {
139 propertyConstraint.initialize(toscaType);
140 propertyConstraint.validate(toscaType, propertyDefinition.getValue());
141 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
142 errorMessages.add(propertyConstraint.getErrorMessage(toscaType, exception, getCompletePropertyName(propertyDefinition)));
145 } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
146 .isValidValue(propertyDefinition.getValue())) {
147 errorMessages.add(String
148 .format("\nUnsupported value provided for %s property supported value " + "type is %s.", getCompletePropertyName(propertyDefinition),
149 toscaType.getType()));
153 private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
154 return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
157 private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
158 if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
159 PropertyDefinition definition = new PropertyDefinition();
160 definition.setValue(propertyDefinition.getValue());
161 definition.setType(dataTypeDefinition.getName());
162 definition.setConstraints(dataTypeDefinition.getConstraints());
163 evaluateConstraintsOnProperty(propertyDefinition);
167 private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
168 List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
170 Map<String, Object> valueMap = MapUtils
171 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
173 if (CollectionUtils.isEmpty(propertyDefinitions)) {
174 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
176 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
178 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
179 logger.debug(e.getMessage(), e);
180 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
184 private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
186 PropertyDefinition newPropertyWithValue;
187 if (valueMap.containsKey(prop.getName()) ) {
188 if (ToscaType.isPrimitiveType(prop.getType())) {
189 newPropertyWithValue = copyPropertyWithNewValue(prop, String.valueOf(valueMap.get(prop.getName())));
190 if (isPropertyToEvaluate(newPropertyWithValue)) {
191 evaluateConstraintsOnProperty(newPropertyWithValue);
193 } else if (ToscaType.isCollectionType(prop.getType())) {
194 newPropertyWithValue =
195 copyPropertyWithNewValue(prop,
196 objectMapper.writeValueAsString(valueMap.get(prop.getName())));
197 if (isPropertyToEvaluate(newPropertyWithValue)) {
198 evaluateCollectionTypeProperties(newPropertyWithValue);
201 newPropertyWithValue =
202 copyPropertyWithNewValue(prop,
203 objectMapper.writeValueAsString(valueMap.get(prop.getName())));
204 if (isPropertyToEvaluate(newPropertyWithValue)) {
205 evaluateComplexTypeProperties(newPropertyWithValue);
209 } catch (IOException | ConstraintValueDoNotMatchPropertyTypeException e) {
210 logger.error(e.getMessage(), e);
211 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
215 private boolean isPropertyToEvaluate(PropertyDefinition propertyDefinition) throws ConstraintValueDoNotMatchPropertyTypeException {
216 if (Boolean.FALSE.equals(propertyDefinition.isRequired())) {
217 if (!ToscaType.isCollectionType(propertyDefinition.getType())) {
218 return StringUtils.isNotEmpty(propertyDefinition.getValue()) &&
219 !"null".equals(propertyDefinition.getValue());
220 } else if (ToscaType.LIST == ToscaType.isValidType(propertyDefinition.getType())) {
221 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
222 return CollectionUtils.isNotEmpty(list);
224 Map<String, Object> valueMap = MapUtils
225 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
227 return MapUtils.isNotEmpty(valueMap);
234 private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
235 ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
237 if (isPropertyToEvaluate(propertyDefinition)) {
238 evaluateCollectionConstraints(propertyDefinition, toscaPropertyType);
240 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
241 logger.error(e.getMessage(), e);
242 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
244 if (ToscaType.LIST == toscaPropertyType) {
245 evaluateListType(propertyDefinition);
246 } else if (ToscaType.MAP == toscaPropertyType) {
247 evaluateMapType(propertyDefinition);
251 private void evaluateCollectionConstraints(PropertyDefinition propertyDefinition, ToscaType toscaPropertyType) {
252 List<PropertyConstraint> constraintsList = propertyDefinition.getConstraints();
254 if (CollectionUtils.isEmpty(constraintsList)) {
257 ToscaType toscaPropertyType1;
258 if (null == toscaPropertyType) {
259 toscaPropertyType1 = ToscaType.isValidType(propertyDefinition.getType());
261 toscaPropertyType1 = toscaPropertyType;
263 constraintsList.stream()
264 .filter(this::isACollectionConstraint)
265 .forEach(propertyConstraint -> {
267 if (ToscaType.LIST == toscaPropertyType1) {
268 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
269 propertyConstraint.validate(list);
270 } else if (ToscaType.MAP == toscaPropertyType1) {
271 final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
272 propertyConstraint.validate(map);
274 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
275 errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaPropertyType1, exception,
276 getCompletePropertyName(propertyDefinition)));
281 private boolean isACollectionConstraint(PropertyConstraint constraint) {
282 if (constraint instanceof MaxLengthConstraint){
285 if (constraint instanceof MinLengthConstraint) {
288 return constraint instanceof LengthConstraint;
291 private void evaluateListType(PropertyDefinition propertyDefinition) {
293 if (propertyDefinition.getSchemaType() == null) {
294 propertyDefinition.setSchema(createStringSchema());
296 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
297 evaluateCollectionType(propertyDefinition, list);
298 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
299 logger.debug(e.getMessage(), e);
300 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
304 private SchemaDefinition createStringSchema() {
305 final SchemaDefinition schemaDefinition = new SchemaDefinition();
306 final PropertyDefinition schemaStringProperty = new PropertyDefinition();
307 schemaStringProperty.setType(ToscaType.STRING.getType());
308 schemaDefinition.setProperty(schemaStringProperty);
309 return schemaDefinition;
312 private void evaluateMapType(final PropertyDefinition propertyDefinition) {
314 if (propertyDefinition.getSchemaType() == null) {
315 propertyDefinition.setSchema(createStringSchema());
317 final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
318 evaluateCollectionType(propertyDefinition, map.values());
319 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
320 logger.debug(e.getMessage(), e);
321 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
325 private void evaluateCollectionPrimitiveSchemaType(final PropertyDefinition propertyDefinition,
326 final String schemaType) throws JsonProcessingException {
327 if (propertyDefinition.getSchema() != null && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
328 propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
329 propertyDefinition.setValue(objectMapper.readValue(propertyDefinition.getValue(), String.class));
330 propertyDefinition.setType(schemaType);
331 evaluateConstraintsOnProperty(propertyDefinition);
335 private void evaluateCollectionType(final PropertyDefinition propertyDefinition, final Collection<Object> valueList) {
336 final String schemaType = propertyDefinition.getSchemaType();
337 for (final Object value : valueList) {
339 final PropertyDefinition propertyCopyWithNewValue = copyPropertyWithNewValue(propertyDefinition, objectMapper.writeValueAsString(value));
340 if (ToscaType.isPrimitiveType(schemaType)) {
341 evaluateCollectionPrimitiveSchemaType(propertyCopyWithNewValue, schemaType);
342 } else if (ToscaType.isCollectionType(schemaType)) {
343 propertyCopyWithNewValue.setType(schemaType);
344 propertyCopyWithNewValue.setSchemaType(propertyDefinition.getSchemaProperty().getSchemaType());
345 evaluateCollectionTypeProperties(propertyCopyWithNewValue);
347 propertyCopyWithNewValue.setType(schemaType);
348 completePropertyName.append(UNDERSCORE);
349 completePropertyName.append(propertyCopyWithNewValue.getName());
350 evaluateComplexTypeProperties(propertyCopyWithNewValue);
352 } catch (final Exception e) {
353 logger.debug(e.getMessage(), e);
354 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
359 private String getCompletePropertyName(final PropertyDefinition propertyDefinition) {
360 if (StringUtils.isNotBlank(completeInputName)) {
361 return completeInputName;
364 final String propertyName = propertyDefinition == null ? "" : propertyDefinition.getName();
365 if (StringUtils.isNotBlank(completePropertyName)) {
366 return completePropertyName + UNDERSCORE + propertyName;
372 private PropertyDefinition copyPropertyWithNewValue(final PropertyDefinition propertyToCopy, final String value) {
373 final var propertyDefinition = new PropertyDefinition(propertyToCopy);
374 propertyDefinition.setValue(value);
375 return propertyDefinition;
378 private PropertyDefinition getPropertyDefinitionObjectFromInputs(PropertyDefinition property) {
379 InputDefinition inputDefinition = (InputDefinition) property;
380 PropertyDefinition propertyDefinition = null;
381 if (CollectionUtils.isEmpty(inputDefinition.getProperties()) || ToscaType.isPrimitiveType(inputDefinition.getProperties().get(0).getType())) {
382 propertyDefinition = new PropertyDefinition();
383 propertyDefinition.setType(inputDefinition.getType());
384 propertyDefinition.setValue(inputDefinition.getDefaultValue());
385 propertyDefinition.setName(inputDefinition.getName());
386 } else if (Objects.nonNull(inputDefinition.getInputPath())) {
387 propertyDefinition = evaluateComplexTypeInputs(inputDefinition);
389 return propertyDefinition;
392 private PropertyDefinition evaluateComplexTypeInputs(InputDefinition inputDefinition) {
393 Map<String, Object> inputMap = new HashMap<>();
394 PropertyDefinition propertyDefinition = new PropertyDefinition();
395 String[] inputPathArr = inputDefinition.getInputPath().split("#");
396 if (inputPathArr.length > 1) {
397 inputPathArr = ArrayUtils.remove(inputPathArr, 0);
400 Map<String, Object> presentMap = inputMap;
401 for (int i = 0; i < inputPathArr.length; i++) {
402 if (i == inputPathArr.length - 1) {
403 presentMap.computeIfAbsent(inputPathArr[i], k -> inputDefinition.getDefaultValue());
405 presentMap.computeIfAbsent(inputPathArr[i], k -> new HashMap<String, Object>());
406 presentMap = (Map<String, Object>) presentMap.get(inputPathArr[i]);
409 if (CollectionUtils.isNotEmpty(inputDefinition.getProperties())) {
410 propertyDefinition.setType(inputDefinition.getProperties().get(0).getType());
412 propertyDefinition.setName(inputDefinition.getName());
413 propertyDefinition.setValue(objectMapper.writeValueAsString(inputMap));
414 } catch (IOException e) {
415 logger.error(e.getMessage(), e);
416 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, inputDefinition.getName()));
418 return propertyDefinition;
421 protected ResponseFormatManager getResponseFormatManager() {
422 return ResponseFormatManager.getInstance();