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.ValidValuesConstraint;
46 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
47 import org.openecomp.sdc.be.model.tosca.constraints.exception.ConstraintViolationException;
48 import org.openecomp.sdc.exception.ResponseFormat;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 public class PropertyValueConstraintValidationUtil {
54 private static final String UNDERSCORE = "_";
55 private static final String VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY = "%nValue provided in invalid format for %s property";
56 private static final Logger logger = LoggerFactory.getLogger(PropertyValueConstraintValidationUtil.class);
57 private static final String IGNORE_PROPERTY_VALUE_START_WITH = "{\"get_input\":";
58 private Map<String, DataTypeDefinition> dataTypeDefinitionCache;
59 private final ObjectMapper objectMapper = new ObjectMapper();
60 private final List<String> errorMessages = new ArrayList<>();
61 private StringBuilder completePropertyName;
62 private String completeInputName;
64 public Either<Boolean, ResponseFormat> validatePropertyConstraints(final Collection<? extends PropertyDefinition> propertyDefinitionList,
65 final ApplicationDataTypeCache applicationDataTypeCache,
68 dataTypeDefinitionCache = applicationDataTypeCache.getAll(model).left().value();
69 CollectionUtils.emptyIfNull(propertyDefinitionList).stream()
70 .filter(this::isValuePresent)
71 .forEach(this::evaluatePropertyTypeForConstraintValidation);
72 if (CollectionUtils.isNotEmpty(errorMessages)) {
73 final String errorMsgAsString = String.join(",", errorMessages);
74 logger.debug("Properties with Invalid Data: {}", errorMsgAsString);
75 return Either.right(getResponseFormatManager().getResponseFormat(ActionStatus.INVALID_PROPERTY_VALUES, errorMsgAsString));
77 return Either.left(Boolean.TRUE);
80 private boolean isValuePresent(PropertyDefinition propertyDefinition) {
81 if (propertyDefinition instanceof ComponentInstanceInput) {
82 return StringUtils.isNotEmpty(propertyDefinition.getValue());
84 if (propertyDefinition instanceof InputDefinition) {
85 return StringUtils.isNotEmpty(propertyDefinition.getDefaultValue());
87 return StringUtils.isNotEmpty(propertyDefinition.getValue());
90 private void evaluatePropertyTypeForConstraintValidation(PropertyDefinition propertyDefinition) {
91 if (propertyDefinition == null || propertyDefinition.getType() == null || !dataTypeDefinitionCache.containsKey(propertyDefinition.getType())) {
92 errorMessages.add("\nUnsupported datatype found for property " + getCompletePropertyName(propertyDefinition));
95 completeInputName = "";
96 completePropertyName = new StringBuilder();
97 if (propertyDefinition instanceof ComponentInstanceInput) {
98 setCompletePropertyName(propertyDefinition);
99 evaluateComplexTypeProperties(propertyDefinition);
102 if (propertyDefinition instanceof InputDefinition) {
103 completeInputName = propertyDefinition.getName();
104 propertyDefinition = getPropertyDefinitionObjectFromInputs(propertyDefinition);
106 if (propertyDefinition != null) {
107 List<PropertyConstraint> propertyConstraints =
108 dataTypeDefinitionCache.get(propertyDefinition.getType()).safeGetConstraints();
109 if (ToscaType.isPrimitiveType(propertyDefinition.getType())) {
110 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
111 propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
112 evaluateConstraintsOnProperty(propertyDefinition);
113 } else if (ToscaType.isCollectionType(propertyDefinition.getType())) {
114 propertyDefinition.setConstraints(org.openecomp.sdc.be.dao.utils.CollectionUtils.merge(propertyDefinition.safeGetConstraints(),
115 propertyConstraints.isEmpty() ? new ArrayList<>() : propertyConstraints));
116 evaluateConstraintsOnProperty(propertyDefinition);
117 evaluateCollectionTypeProperties(propertyDefinition);
119 setCompletePropertyName(propertyDefinition);
120 evaluateComplexTypeProperties(propertyDefinition);
125 private void setCompletePropertyName(PropertyDefinition propertyDefinition) {
126 if (StringUtils.isNotBlank(propertyDefinition.getUniqueId())) {
127 completePropertyName.append(propertyDefinition.getUniqueId().substring(propertyDefinition.getUniqueId().lastIndexOf('.') + 1));
131 private void evaluateConstraintsOnProperty(PropertyDefinition propertyDefinition) {
132 ToscaType toscaType = ToscaType.isValidType(propertyDefinition.getType());
133 if (isPropertyNotMappedAsInput(propertyDefinition) && CollectionUtils.isNotEmpty(propertyDefinition.getConstraints())) {
134 for (PropertyConstraint propertyConstraint : propertyDefinition.getConstraints()) {
136 propertyConstraint.initialize(toscaType);
137 propertyConstraint.validate(toscaType, propertyDefinition.getValue());
138 } catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException exception) {
139 errorMessages.add("\n" + propertyConstraint.getErrorMessage(toscaType, exception, getCompletePropertyName(propertyDefinition)));
142 } else if (isPropertyNotMappedAsInput(propertyDefinition) && ToscaType.isPrimitiveType(propertyDefinition.getType()) && !toscaType
143 .isValidValue(propertyDefinition.getValue())) {
144 errorMessages.add(String
145 .format("\nUnsupported value provided for %s property supported value " + "type is %s.", getCompletePropertyName(propertyDefinition),
146 toscaType.getType()));
150 private boolean isPropertyNotMappedAsInput(PropertyDefinition propertyDefinition) {
151 return !propertyDefinition.getValue().startsWith(IGNORE_PROPERTY_VALUE_START_WITH);
154 private void checkAndEvaluatePrimitiveProperty(PropertyDefinition propertyDefinition, DataTypeDefinition dataTypeDefinition) {
155 if (ToscaType.isPrimitiveType(dataTypeDefinition.getName()) && CollectionUtils.isNotEmpty(dataTypeDefinition.getConstraints())) {
156 PropertyDefinition definition = new PropertyDefinition();
157 definition.setValue(propertyDefinition.getValue());
158 definition.setType(dataTypeDefinition.getName());
159 definition.setConstraints(dataTypeDefinition.getConstraints());
160 evaluateConstraintsOnProperty(propertyDefinition);
164 private void evaluateComplexTypeProperties(PropertyDefinition propertyDefinition) {
165 List<PropertyDefinition> propertyDefinitions = dataTypeDefinitionCache.get(propertyDefinition.getType()).getProperties();
167 Map<String, Object> valueMap = MapUtils
168 .emptyIfNull(ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {
170 if (CollectionUtils.isEmpty(propertyDefinitions)) {
171 checkAndEvaluatePrimitiveProperty(propertyDefinition, dataTypeDefinitionCache.get(propertyDefinition.getType()));
173 ListUtils.emptyIfNull(propertyDefinitions).forEach(prop -> evaluateRegularComplexType(propertyDefinition, prop, valueMap));
175 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
176 logger.debug(e.getMessage(), e);
177 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
181 private void evaluateRegularComplexType(PropertyDefinition propertyDefinition, PropertyDefinition prop, Map<String, Object> valueMap) {
183 if (valueMap.containsKey(prop.getName())) {
184 if (ToscaType.isPrimitiveType(prop.getType())) {
185 evaluateConstraintsOnProperty(copyPropertyWithNewValue(prop, String.valueOf(valueMap.get(prop.getName()))));
186 } else if (ToscaType.isCollectionType(prop.getType())) {
187 evaluateCollectionTypeProperties(copyPropertyWithNewValue(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
189 completePropertyName.append(UNDERSCORE);
190 completePropertyName.append(prop.getName());
191 evaluateComplexTypeProperties(copyPropertyWithNewValue(prop, objectMapper.writeValueAsString(valueMap.get(prop.getName()))));
194 } catch (IOException e) {
195 logger.error(e.getMessage(), e);
196 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
200 private void evaluateCollectionTypeProperties(PropertyDefinition propertyDefinition) {
201 ToscaType toscaPropertyType = ToscaType.isValidType(propertyDefinition.getType());
202 if (ToscaType.LIST == toscaPropertyType) {
203 evaluateListType(propertyDefinition);
204 } else if (ToscaType.MAP == toscaPropertyType) {
205 evaluateMapType(propertyDefinition);
209 private void evaluateListType(PropertyDefinition propertyDefinition) {
211 if (propertyDefinition.getSchemaType() == null) {
212 propertyDefinition.setSchema(createStringSchema());
214 List<Object> list = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
215 evaluateCollectionType(propertyDefinition, list);
216 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
217 logger.debug(e.getMessage(), e);
218 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
222 private SchemaDefinition createStringSchema() {
223 final SchemaDefinition schemaDefinition = new SchemaDefinition();
224 final PropertyDefinition schemaStringProperty = new PropertyDefinition();
225 schemaStringProperty.setType(ToscaType.STRING.getType());
226 schemaDefinition.setProperty(schemaStringProperty);
227 return schemaDefinition;
230 private void evaluateMapType(final PropertyDefinition propertyDefinition) {
232 if (propertyDefinition.getSchemaType() == null) {
233 propertyDefinition.setSchema(createStringSchema());
235 final Map<String, Object> map = ConstraintUtil.parseToCollection(propertyDefinition.getValue(), new TypeReference<>() {});
236 evaluateCollectionType(propertyDefinition, map.values());
237 } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
238 logger.debug(e.getMessage(), e);
239 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
243 private void evaluateCollectionPrimitiveSchemaType(final PropertyDefinition propertyDefinition,
244 final String schemaType) throws JsonProcessingException {
245 if (propertyDefinition.getSchema() != null && propertyDefinition.getSchema().getProperty() instanceof PropertyDefinition) {
246 propertyDefinition.setConstraints(((PropertyDefinition) propertyDefinition.getSchema().getProperty()).getConstraints());
247 propertyDefinition.setValue(objectMapper.readValue(propertyDefinition.getValue(), String.class));
248 propertyDefinition.setType(schemaType);
249 evaluateConstraintsOnProperty(propertyDefinition);
253 private void evaluateCollectionType(final PropertyDefinition propertyDefinition, final Collection<Object> valueList) {
254 final String schemaType = propertyDefinition.getSchemaType();
255 for (final Object value : valueList) {
257 final PropertyDefinition propertyCopyWithNewValue = copyPropertyWithNewValue(propertyDefinition, objectMapper.writeValueAsString(value));
258 if (ToscaType.isPrimitiveType(schemaType)) {
259 evaluateCollectionPrimitiveSchemaType(propertyCopyWithNewValue, schemaType);
260 } else if (ToscaType.isCollectionType(schemaType)) {
261 propertyCopyWithNewValue.setType(schemaType);
262 propertyCopyWithNewValue.setSchemaType(propertyDefinition.getSchemaProperty().getSchemaType());
263 evaluateCollectionTypeProperties(propertyCopyWithNewValue);
265 propertyCopyWithNewValue.setType(schemaType);
266 completePropertyName.append(UNDERSCORE);
267 completePropertyName.append(propertyCopyWithNewValue.getName());
268 evaluateComplexTypeProperties(propertyCopyWithNewValue);
270 } catch (final Exception e) {
271 logger.debug(e.getMessage(), e);
272 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, getCompletePropertyName(propertyDefinition)));
277 private String getCompletePropertyName(final PropertyDefinition propertyDefinition) {
278 if (StringUtils.isNotBlank(completeInputName)) {
279 return completeInputName;
282 final String propertyName = propertyDefinition == null ? "" : propertyDefinition.getName();
283 if (StringUtils.isNotBlank(completePropertyName)) {
284 return completePropertyName + UNDERSCORE + propertyName;
290 private PropertyDefinition copyPropertyWithNewValue(final PropertyDefinition propertyToCopy, final String value) {
291 final var propertyDefinition = new PropertyDefinition(propertyToCopy);
292 propertyDefinition.setValue(value);
293 return propertyDefinition;
296 private boolean isValidValueConstraintPresent(List<PropertyConstraint> propertyConstraints) {
297 return propertyConstraints != null && propertyConstraints.stream().anyMatch(ValidValuesConstraint.class::isInstance);
300 private PropertyDefinition getPropertyDefinitionObjectFromInputs(PropertyDefinition property) {
301 InputDefinition inputDefinition = (InputDefinition) property;
302 PropertyDefinition propertyDefinition = null;
303 if (CollectionUtils.isEmpty(inputDefinition.getProperties()) || ToscaType.isPrimitiveType(inputDefinition.getProperties().get(0).getType())) {
304 propertyDefinition = new PropertyDefinition();
305 propertyDefinition.setType(inputDefinition.getType());
306 propertyDefinition.setValue(inputDefinition.getDefaultValue());
307 propertyDefinition.setName(inputDefinition.getName());
308 } else if (Objects.nonNull(inputDefinition.getInputPath())) {
309 propertyDefinition = evaluateComplexTypeInputs(inputDefinition);
311 return propertyDefinition;
314 private PropertyDefinition evaluateComplexTypeInputs(InputDefinition inputDefinition) {
315 Map<String, Object> inputMap = new HashMap<>();
316 PropertyDefinition propertyDefinition = new PropertyDefinition();
317 String[] inputPathArr = inputDefinition.getInputPath().split("#");
318 if (inputPathArr.length > 1) {
319 inputPathArr = ArrayUtils.remove(inputPathArr, 0);
322 Map<String, Object> presentMap = inputMap;
323 for (int i = 0; i < inputPathArr.length; i++) {
324 if (i == inputPathArr.length - 1) {
325 presentMap.computeIfAbsent(inputPathArr[i], k -> inputDefinition.getDefaultValue());
327 presentMap.computeIfAbsent(inputPathArr[i], k -> new HashMap<String, Object>());
328 presentMap = (Map<String, Object>) presentMap.get(inputPathArr[i]);
331 if (CollectionUtils.isNotEmpty(inputDefinition.getProperties())) {
332 propertyDefinition.setType(inputDefinition.getProperties().get(0).getType());
334 propertyDefinition.setName(inputDefinition.getName());
335 propertyDefinition.setValue(objectMapper.writeValueAsString(inputMap));
336 } catch (IOException e) {
337 logger.error(e.getMessage(), e);
338 errorMessages.add(String.format(VALUE_PROVIDED_IN_INVALID_FORMAT_FOR_PROPERTY, inputDefinition.getName()));
340 return propertyDefinition;
343 protected ResponseFormatManager getResponseFormatManager() {
344 return ResponseFormatManager.getInstance();