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.HashMap;
26 import java.util.List;
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.datatypes.elements.SchemaDefinition;
37 import org.openecomp.sdc.be.model.ComponentInstanceInput;
38 import org.openecomp.sdc.be.model.DataTypeDefinition;
39 import org.openecomp.sdc.be.model.InputDefinition;
40 import org.openecomp.sdc.be.model.PropertyConstraint;
41 import org.openecomp.sdc.be.model.PropertyDefinition;
42 import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache;
43 import org.openecomp.sdc.be.model.tosca.ToscaType;
44 import org.openecomp.sdc.be.model.tosca.constraints.ConstraintUtil;
45 import org.openecomp.sdc.be.model.tosca.constraints.LengthConstraint;
46 import org.openecomp.sdc.be.model.tosca.constraints.MaxLengthConstraint;
47 import org.openecomp.sdc.be.model.tosca.constraints.MinLengthConstraint;
48 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
49 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
50 import org.openecomp.sdc.exception.ResponseFormat;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 public class PropertyValueConstraintValidationUtil {
56 private static final String UNDERSCORE = "_";
57 private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
58 private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
59 private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
60 private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
61 private final ObjectMapper objectMapper = new ObjectMapper();
62 private final List<String> errorMessages = new ArrayList<>();
63 private StringBuilder completePropertyName;
64 private String completeInputName;
66 public Either<Boolean, ResponseFormat> validatePropertyConstraints(final Collection<? extends PropertyDefinition> propertyDefinitionList,
67 final ApplicationDataTypeCache applicationDataTypeCache,
70 dataTypeDefinitionCache = applicationDataTypeCache.getAll(model).left().value();
71 CollectionUtils.emptyIfNull(propertyDefinitionList).stream()
72 .filter(this::isValuePresent)
73 .forEach(this::evaluatePropertyTypeForConstraintValidation);
74 if (CollectionUtils.isNotEmpty(errorMessages)) {
75 final String errorMsgAsString = String.join(",", errorMessages);
76 logger.debug("Properties with Invalid Data: {}", errorMsgAsString);
77 return Either.right(getResponseFormatManager().getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, errorMsgAsString));
79 return Either.left(Boolean.TRUE);
82 private boolean isValuePresent(PropertyDefinition propertyDefinition) {
83 if (propertyDefinition instanceof ComponentInstanceInput) {
84 return StringUtils.isNotEmpty(propertyDefinition.getValue());
86 if (propertyDefinition instanceof InputDefinition) {
87 return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
89 return StringUtils.isNotEmpty(propertyDefinition.getValue());
92 private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
93 if (propertyDefinition == null || propertyDefinition.getType() == null || !dataTypeDefinitionCache.containsKey(
94 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)));
143 } catch (IllegalArgumentException ie) {
144 errorMessages.add(ie.getMessage());
147 } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
148 .isValidValue(propertyDefinition.getValue())) {
149 errorMessages.add(String.format("Unsupported value provided for %s property supported value type is %s.",
150 getCompletePropertyName(propertyDefinition), toscaType.getType()));
154 private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
155 return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
158 private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
159 if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
160 PropertyDefinition definition = new PropertyDefinition();
161 definition.setValue(propertyDefinition.getValue());
162 definition.setType(dataTypeDefinition.getName());
163 definition.setConstraints(dataTypeDefinition.getConstraints());
164 evaluateConstraintsOnProperty(propertyDefinition);
168 private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
169 List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
171 Map<String, Object> valueMap = MapUtils
172 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
174 if (CollectionUtils.isEmpty(propertyDefinitions)) {
175 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
177 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
179 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
180 logger.debug(e.getMessage(), e);
181 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
185 private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
187 PropertyDefinition newPropertyWithValue;
188 if (valueMap.containsKey(prop.getName())) {
189 if (ToscaType.isPrimitiveType(prop.getType())) {
190 newPropertyWithValue = copyPropertyWithNewValue(prop, String.valueOf(valueMap.get(prop.getName())));
191 if (isPropertyToEvaluate(newPropertyWithValue)) {
192 evaluateConstraintsOnProperty(newPropertyWithValue);
194 } else if (ToscaType.isCollectionType(prop.getType())) {
195 newPropertyWithValue =
196 copyPropertyWithNewValue(prop,
197 objectMapper.writeValueAsString(valueMap.get(prop.getName())));
198 if (isPropertyToEvaluate(newPropertyWithValue)) {
199 evaluateCollectionTypeProperties(newPropertyWithValue);
202 newPropertyWithValue =
203 copyPropertyWithNewValue(prop,
204 objectMapper.writeValueAsString(valueMap.get(prop.getName())));
205 if (isPropertyToEvaluate(newPropertyWithValue)) {
206 evaluateComplexTypeProperties(newPropertyWithValue);
210 } catch (IOException | ConstraintValueDoNotMatchPropertyTypeException e) {
211 logger.error(e.getMessage(), e);
212 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
216 private boolean isPropertyToEvaluate(PropertyDefinition propertyDefinition) throws ConstraintValueDoNotMatchPropertyTypeException {
217 if (Boolean.FALSE.equals(propertyDefinition.isRequired())) {
218 if (!ToscaType.isCollectionType(propertyDefinition.getType())) {
219 return StringUtils.isNotEmpty(propertyDefinition.getValue()) &&
220 !"null".equals(propertyDefinition.getValue());
221 } else if (ToscaType.LIST == ToscaType.isValidType(propertyDefinition.getType())) {
222 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
224 return CollectionUtils.isNotEmpty(list);
226 Map<String, Object> valueMap = MapUtils
227 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
229 return MapUtils.isNotEmpty(valueMap);
236 private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
237 ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
239 if (isPropertyToEvaluate(propertyDefinition)) {
240 evaluateCollectionConstraints(propertyDefinition, toscaPropertyType);
242 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
243 logger.error(e.getMessage(), e);
244 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
246 if (ToscaType.LIST == toscaPropertyType) {
247 evaluateListType(propertyDefinition);
248 } else if (ToscaType.MAP == toscaPropertyType) {
249 evaluateMapType(propertyDefinition);
253 private void evaluateCollectionConstraints(PropertyDefinition propertyDefinition, ToscaType toscaPropertyType) {
254 List<PropertyConstraint> constraintsList = propertyDefinition.getConstraints();
256 if (CollectionUtils.isEmpty(constraintsList)) {
259 ToscaType toscaPropertyType1;
260 if (null == toscaPropertyType) {
261 toscaPropertyType1 = ToscaType.isValidType(propertyDefinition.getType());
263 toscaPropertyType1 = toscaPropertyType;
265 constraintsList.stream()
266 .filter(this::isACollectionConstraint)
267 .forEach(propertyConstraint -> {
269 if (ToscaType.LIST == toscaPropertyType1) {
270 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
272 propertyConstraint.validate(list);
273 } else if (ToscaType.MAP == toscaPropertyType1) {
274 final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
276 propertyConstraint.validate(map);
278 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
279 errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaPropertyType1, exception,
280 getCompletePropertyName(propertyDefinition)));
285 private boolean isACollectionConstraint(PropertyConstraint constraint) {
286 if (constraint instanceof MaxLengthConstraint) {
289 if (constraint instanceof MinLengthConstraint) {
292 return constraint instanceof LengthConstraint;
295 private void evaluateListType(PropertyDefinition propertyDefinition) {
297 if (propertyDefinition.getSchemaType() == null) {
298 propertyDefinition.setSchema(createStringSchema());
300 Collection<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
302 evaluateCollectionType(propertyDefinition, list);
303 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
304 logger.debug(e.getMessage(), e);
305 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
309 private SchemaDefinition createStringSchema() {
310 final SchemaDefinition schemaDefinition = new SchemaDefinition();
311 final PropertyDefinition schemaStringProperty = new PropertyDefinition();
312 schemaStringProperty.setType(ToscaType.STRING.getType());
313 schemaDefinition.setProperty(schemaStringProperty);
314 return schemaDefinition;
317 private void evaluateMapType(final PropertyDefinition propertyDefinition) {
319 if (propertyDefinition.getSchemaType() == null) {
320 propertyDefinition.setSchema(createStringSchema());
322 final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
324 evaluateCollectionType(propertyDefinition, map.values());
325 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
326 logger.debug(e.getMessage(), e);
327 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
331 private void evaluateCollectionPrimitiveSchemaType(final PropertyDefinition propertyDefinition,
332 final String schemaType) throws JsonProcessingException {
333 if (propertyDefinition.getSchema() != null && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
334 propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
335 propertyDefinition.setValue(objectMapper.readValue(propertyDefinition.getValue(), String.class));
336 propertyDefinition.setType(schemaType);
337 evaluateConstraintsOnProperty(propertyDefinition);
341 private void evaluateCollectionType(final PropertyDefinition propertyDefinition, final Collection<Object> valueList) {
342 final String schemaType = propertyDefinition.getSchemaType();
343 for (final Object value : valueList) {
345 final PropertyDefinition propertyCopyWithNewValue = copyPropertyWithNewValue(propertyDefinition,
346 objectMapper.writeValueAsString(value));
347 if (ToscaType.isPrimitiveType(schemaType)) {
348 evaluateCollectionPrimitiveSchemaType(propertyCopyWithNewValue, schemaType);
349 } else if (ToscaType.isCollectionType(schemaType)) {
350 propertyCopyWithNewValue.setType(schemaType);
351 propertyCopyWithNewValue.setSchemaType(propertyDefinition.getSchemaProperty().getSchemaType());
352 evaluateCollectionTypeProperties(propertyCopyWithNewValue);
354 propertyCopyWithNewValue.setType(schemaType);
355 completePropertyName.append(UNDERSCORE);
356 completePropertyName.append(propertyCopyWithNewValue.getName());
357 evaluateComplexTypeProperties(propertyCopyWithNewValue);
359 } catch (final Exception e) {
360 logger.debug(e.getMessage(), e);
361 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
366 private String getCompletePropertyName(final PropertyDefinition propertyDefinition) {
367 if (StringUtils.isNotBlank(completeInputName)) {
368 return completeInputName;
371 final String propertyName = propertyDefinition == null ? "" : propertyDefinition.getName();
372 if (StringUtils.isNotBlank(completePropertyName)) {
373 return completePropertyName + UNDERSCORE + propertyName;
379 private PropertyDefinition copyPropertyWithNewValue(final PropertyDefinition propertyToCopy, final String value) {
380 final var propertyDefinition = new PropertyDefinition(propertyToCopy);
381 propertyDefinition.setValue(value);
382 return propertyDefinition;
385 private PropertyDefinition getPropertyDefinitionObjectFromInputs(PropertyDefinition property) {
386 InputDefinition inputDefinition = (InputDefinition) property;
387 PropertyDefinition propertyDefinition = null;
388 if (CollectionUtils.isEmpty(inputDefinition.getProperties()) || ToscaType.isPrimitiveType(inputDefinition.getProperties().get(0).getType())) {
389 propertyDefinition = new PropertyDefinition();
390 propertyDefinition.setType(inputDefinition.getType());
391 propertyDefinition.setValue(inputDefinition.getDefaultValue());
392 propertyDefinition.setName(inputDefinition.getName());
393 } else if (Objects.nonNull(inputDefinition.getInputPath())) {
394 propertyDefinition = evaluateComplexTypeInputs(inputDefinition);
396 return propertyDefinition;
399 private PropertyDefinition evaluateComplexTypeInputs(InputDefinition inputDefinition) {
400 Map<String, Object> inputMap = new HashMap<>();
401 PropertyDefinition propertyDefinition = new PropertyDefinition();
402 String[] inputPathArr = inputDefinition.getInputPath().split("#");
403 if (inputPathArr.length > 1) {
404 inputPathArr = ArrayUtils.remove(inputPathArr, 0);
407 Map<String, Object> presentMap = inputMap;
408 for (int i = 0; i < inputPathArr.length; i++) {
409 if (i == inputPathArr.length - 1) {
410 presentMap.computeIfAbsent(inputPathArr[i], k -> inputDefinition.getDefaultValue());
412 presentMap.computeIfAbsent(inputPathArr[i], k -> new HashMap<String, Object>());
413 presentMap = (Map<String, Object>) presentMap.get(inputPathArr[i]);
416 if (CollectionUtils.isNotEmpty(inputDefinition.getProperties())) {
417 propertyDefinition.setType(inputDefinition.getProperties().get(0).getType());
419 propertyDefinition.setName(inputDefinition.getName());
420 propertyDefinition.setValue(objectMapper.writeValueAsString(inputMap));
421 } catch (IOException e) {
422 logger.error(e.getMessage(), e);
423 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, inputDefinition.getName()));
425 return propertyDefinition;
428 protected ResponseFormatManager getResponseFormatManager() {
429 return ResponseFormatManager.getInstance();