Support adding data types to model
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / operations / impl / AttributeOperation.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  SDC
4  *  Copyright (C) 2020 Nordix Foundation
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.model.operations.impl;
21
22 import com.google.gson.JsonElement;
23 import fj.data.Either;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import org.apache.commons.lang3.tuple.ImmutablePair;
30 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
31 import org.openecomp.sdc.be.dao.janusgraph.HealingJanusGraphGenericDao;
32 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
33 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
34 import org.openecomp.sdc.be.dao.neo4j.GraphPropertiesDictionary;
35 import org.openecomp.sdc.be.datatypes.elements.AttributeDataDefinition;
36 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
37 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
38 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
39 import org.openecomp.sdc.be.model.DataTypeDefinition;
40 import org.openecomp.sdc.be.model.PropertyDefinition;
41 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
42 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
43 import org.openecomp.sdc.be.model.tosca.converters.PropertyValueConverter;
44 import org.openecomp.sdc.be.resources.data.DataTypeData;
45 import org.openecomp.sdc.be.resources.data.PropertyData;
46 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
47 import org.openecomp.sdc.common.log.wrappers.Logger;
48 import org.springframework.beans.factory.annotation.Autowired;
49 import org.springframework.stereotype.Component;
50
51 @Component("attribute-operation")
52 public class AttributeOperation extends AbstractOperation {
53
54     private static final String FAILED_TO_FETCH_ATTRIBUTES_OF_DATA_TYPE = "Failed to fetch attributes of data type {}";
55     private static final String DATA_TYPE_CANNOT_BE_FOUND_IN_GRAPH_STATUS_IS = "Data type {} cannot be found in graph. status is {}";
56     private static final String THE_VALUE_OF_ATTRIBUTE_FROM_TYPE_IS_INVALID = "The value {} of attribute from type {} is invalid";
57     private static final Logger log = Logger.getLogger(AttributeOperation.class.getName());
58
59     @Autowired
60     public AttributeOperation(HealingJanusGraphGenericDao janusGraphGenericDao) {
61         this.janusGraphGenericDao = janusGraphGenericDao;
62     }
63
64     public boolean isAttributeTypeValid(final AttributeDataDefinition attributeDefinition) {
65         if (attributeDefinition == null) {
66             return false;
67         }
68         if (ToscaPropertyType.isValidType(attributeDefinition.getType()) == null) {
69             final Either<Boolean, JanusGraphOperationStatus> definedInDataTypes = isDefinedInDataTypes(attributeDefinition.getType());
70             if (definedInDataTypes.isRight()) {
71                 return false;
72             } else {
73                 Boolean isExist = definedInDataTypes.left().value();
74                 return isExist.booleanValue();
75             }
76         }
77         return true;
78     }
79
80     private Either<Boolean, JanusGraphOperationStatus> isDefinedInDataTypes(final String propertyType) {
81         final String dataTypeUid = UniqueIdBuilder.buildDataTypeUid(null, propertyType);
82         final Either<DataTypeDefinition, JanusGraphOperationStatus> dataTypeByUid = getDataTypeByUid(dataTypeUid);
83         if (dataTypeByUid.isRight()) {
84             final JanusGraphOperationStatus status = dataTypeByUid.right().value();
85             if (status == JanusGraphOperationStatus.NOT_FOUND) {
86                 return Either.left(false);
87             }
88             return Either.right(status);
89         }
90         return Either.left(true);
91     }
92
93     /**
94      * Build Data type object from graph by unique id
95      */
96     private Either<DataTypeDefinition, JanusGraphOperationStatus> getDataTypeByUid(final String uniqueId) {
97         final Either<DataTypeData, JanusGraphOperationStatus> dataTypesRes = janusGraphGenericDao
98             .getNode(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.DataType), uniqueId, DataTypeData.class);
99         if (dataTypesRes.isRight()) {
100             JanusGraphOperationStatus status = dataTypesRes.right().value();
101             log.debug(DATA_TYPE_CANNOT_BE_FOUND_IN_GRAPH_STATUS_IS, uniqueId, status);
102             return Either.right(status);
103         }
104         final DataTypeData ctData = dataTypesRes.left().value();
105         final DataTypeDefinition dataTypeDefinition = new DataTypeDefinition(ctData.getDataTypeDataDefinition());
106         final JanusGraphOperationStatus propertiesStatus = fillProperties(uniqueId, dataTypeDefinition);
107         if (propertiesStatus != JanusGraphOperationStatus.OK) {
108             log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, FAILED_TO_FETCH_ATTRIBUTES_OF_DATA_TYPE, uniqueId);
109             return Either.right(propertiesStatus);
110         }
111         final Either<ImmutablePair<DataTypeData, GraphEdge>, JanusGraphOperationStatus> parentNode = janusGraphGenericDao
112             .getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.DataType), uniqueId, GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.DataType, DataTypeData.class);
113         log.debug("After retrieving DERIVED_FROM node of {}. status is {}", uniqueId, parentNode);
114         if (parentNode.isRight()) {
115             final JanusGraphOperationStatus janusGraphOperationStatus = parentNode.right().value();
116             if (janusGraphOperationStatus != JanusGraphOperationStatus.NOT_FOUND) {
117                 log.error(EcompLoggerErrorCode.BUSINESS_PROCESS_ERROR, "Failed to find the parent data type of data type {}. status is {}", uniqueId, janusGraphOperationStatus);
118                 return Either.right(janusGraphOperationStatus);
119             }
120         } else {
121             // derived from node was found
122             final ImmutablePair<DataTypeData, GraphEdge> immutablePair = parentNode.left().value();
123             final DataTypeData parentCT = immutablePair.getKey();
124             final String parentUniqueId = parentCT.getUniqueId();
125             final Either<DataTypeDefinition, JanusGraphOperationStatus> dataTypeByUid = getDataTypeByUid(parentUniqueId);
126             if (dataTypeByUid.isRight()) {
127                 return Either.right(dataTypeByUid.right().value());
128             }
129             final DataTypeDefinition parentDataTypeDefinition = dataTypeByUid.left().value();
130             dataTypeDefinition.setDerivedFrom(parentDataTypeDefinition);
131         }
132         return Either.left(dataTypeDefinition);
133     }
134
135     private JanusGraphOperationStatus fillProperties(final String uniqueId, final DataTypeDefinition dataTypeDefinition) {
136         final Either<Map<String, PropertyDefinition>, JanusGraphOperationStatus> findPropertiesOfNode = this.findPropertiesOfNode(NodeTypeEnum.DataType, uniqueId);
137         if (findPropertiesOfNode.isRight()) {
138             final JanusGraphOperationStatus janusGraphOperationStatus = findPropertiesOfNode.right().value();
139             log.debug("After looking for properties of vertex {}. status is {}", uniqueId, janusGraphOperationStatus);
140             if (JanusGraphOperationStatus.NOT_FOUND.equals(janusGraphOperationStatus)) {
141                 return JanusGraphOperationStatus.OK;
142             } else {
143                 return janusGraphOperationStatus;
144             }
145         } else {
146             final Map<String, PropertyDefinition> properties = findPropertiesOfNode.left().value();
147             if (properties != null && !properties.isEmpty()) {
148                 List<PropertyDefinition> listOfProps = new ArrayList<>();
149                 for (final Entry<String, PropertyDefinition> entry : properties.entrySet()) {
150                     final String propName = entry.getKey();
151                     final PropertyDefinition propertyDefinition = entry.getValue();
152                     final PropertyDefinition newPropertyDefinition = new PropertyDefinition(propertyDefinition);
153                     newPropertyDefinition.setName(propName);
154                     listOfProps.add(newPropertyDefinition);
155                 }
156                 dataTypeDefinition.setProperties(listOfProps);
157             }
158             return JanusGraphOperationStatus.OK;
159         }
160     }
161
162     private Either<Map<String, PropertyDefinition>, JanusGraphOperationStatus> findPropertiesOfNode(final NodeTypeEnum nodeType,
163                                                                                                     final String uniqueId) {
164         final Map<String, PropertyDefinition> resourceProps = new HashMap<>();
165         final Either<List<ImmutablePair<PropertyData, GraphEdge>>, JanusGraphOperationStatus> childrenNodes = janusGraphGenericDao
166             .getChildrenNodes(UniqueIdBuilder.getKeyByNodeType(nodeType), uniqueId, GraphEdgeLabels.PROPERTY, NodeTypeEnum.Property, PropertyData.class);
167         if (childrenNodes.isRight()) {
168             final JanusGraphOperationStatus operationStatus = childrenNodes.right().value();
169             return Either.right(operationStatus);
170         }
171         final List<ImmutablePair<PropertyData, GraphEdge>> values = childrenNodes.left().value();
172         if (values != null) {
173             for (final ImmutablePair<PropertyData, GraphEdge> immutablePair : values) {
174                 final GraphEdge edge = immutablePair.getValue();
175                 final String propertyName = (String) edge.getProperties().get(GraphPropertiesDictionary.NAME.getProperty());
176                 log.debug("Attribute {} is associated to node {}", propertyName, uniqueId);
177                 final PropertyData propertyData = immutablePair.getKey();
178                 final PropertyDefinition propertyDefinition = this.convertPropertyDataToPropertyDefinition(propertyData, propertyName);
179                 resourceProps.put(propertyName, propertyDefinition);
180             }
181         }
182         log.debug("The properties associated to node {} are {}", uniqueId, resourceProps);
183         return Either.left(resourceProps);
184     }
185
186     private PropertyDefinition convertPropertyDataToPropertyDefinition(final PropertyData propertyDataResult, final String propertyName) {
187         log.debug("The object returned after create property is {}", propertyDataResult);
188         final PropertyDefinition propertyDefResult = new PropertyDefinition(propertyDataResult.getPropertyDataDefinition());
189         propertyDefResult.setConstraints(convertConstraints(propertyDataResult.getConstraints()));
190         propertyDefResult.setName(propertyName);
191         return propertyDefResult;
192     }
193
194     public ImmutablePair<String, Boolean> isAttributeInnerTypeValid(final AttributeDataDefinition attributeDefinition,
195                                                                     final Map<String, DataTypeDefinition> dataTypes) {
196         if (attributeDefinition == null) {
197             return new ImmutablePair<>(null, false);
198         }
199         SchemaDefinition schema;
200         PropertyDataDefinition innerProp;
201         String innerType = null;
202         if ((schema = attributeDefinition.getSchema()) != null && ((innerProp = schema.getProperty()) != null)) {
203             innerType = innerProp.getType();
204         }
205         final ToscaPropertyType innerToscaType = ToscaPropertyType.isValidType(innerType);
206         if (innerToscaType == null) {
207             final DataTypeDefinition dataTypeDefinition = dataTypes.get(innerType);
208             if (dataTypeDefinition == null) {
209                 log.debug("The inner type {} is not a data type.", innerType);
210                 return new ImmutablePair<>(innerType, false);
211             } else {
212                 log.debug("The inner type {} is a data type. Data type definition is {}", innerType, dataTypeDefinition);
213             }
214         }
215         return new ImmutablePair<>(innerType, true);
216     }
217
218     public boolean isAttributeDefaultValueValid(final AttributeDataDefinition attributeDefinition, final Map<String, DataTypeDefinition> dataTypes) {
219         if (attributeDefinition == null) {
220             return false;
221         }
222         boolean isValid;
223         String innerType = null;
224         final String propertyType = attributeDefinition.getType();
225         final ToscaPropertyType type = getType(propertyType);
226         if (type == ToscaPropertyType.LIST || type == ToscaPropertyType.MAP) {
227             final SchemaDefinition def = attributeDefinition.getSchema();
228             if (def == null) {
229                 return false;
230             }
231             final PropertyDataDefinition propDef = def.getProperty();
232             if (propDef == null) {
233                 return false;
234             }
235             innerType = propDef.getType();
236         }
237         final String value = (String) attributeDefinition.get_default();
238         if (type != null) {
239             isValid = isValidValue(type, value, innerType, dataTypes);
240         } else {
241             log.trace("The given type {} is not a pre defined one.", propertyType);
242             final DataTypeDefinition foundDt = dataTypes.get(propertyType);
243             if (foundDt != null) {
244                 isValid = isValidComplexValue(foundDt, value, dataTypes);
245             } else {
246                 isValid = false;
247             }
248         }
249         return isValid;
250     }
251
252     private boolean isValidComplexValue(final DataTypeDefinition foundDt, final String value, final Map<String, DataTypeDefinition> dataTypes) {
253         final ImmutablePair<JsonElement, Boolean> validateAndUpdate = dataTypeValidatorConverter.validateAndUpdate(value, foundDt, dataTypes);
254         log.trace("The result after validating complex value of type {} is {}", foundDt.getName(), validateAndUpdate);
255         return validateAndUpdate.right.booleanValue();
256     }
257
258     public StorageOperationStatus validateAndUpdateAttribute(final AttributeDataDefinition attributeDefinition,
259                                                              final Map<String, DataTypeDefinition> dataTypes) {
260         log.trace("Going to validate attribute type and value. {}", attributeDefinition);
261         final String attributeDefinitionType = attributeDefinition.getType();
262         final String value = (String) attributeDefinition.get_default();
263         final ToscaPropertyType type = getType(attributeDefinitionType);
264         if (type == null) {
265             final DataTypeDefinition dataTypeDefinition = dataTypes.get(attributeDefinitionType);
266             if (dataTypeDefinition == null) {
267                 log.debug("The type {} of attribute cannot be found.", attributeDefinitionType);
268                 return StorageOperationStatus.INVALID_TYPE;
269             }
270             return validateAndUpdateAttributeComplexValue(attributeDefinition, attributeDefinitionType, value, dataTypeDefinition, dataTypes);
271         }
272         String innerType;
273         final Either<String, JanusGraphOperationStatus> checkInnerType = getInnerType(type, attributeDefinition::getSchema);
274         if (checkInnerType.isRight()) {
275             return StorageOperationStatus.INVALID_TYPE;
276         }
277         innerType = checkInnerType.left().value();
278         log.trace("After validating property type {}", attributeDefinitionType);
279         if (!isValidValue(type, value, innerType, dataTypes)) {
280             log.info(THE_VALUE_OF_ATTRIBUTE_FROM_TYPE_IS_INVALID, value, type);
281             return StorageOperationStatus.INVALID_VALUE;
282         }
283         final PropertyValueConverter converter = type.getConverter();
284         if (isEmptyValue(value)) {
285             log.debug("Default value was not sent for attribute {}. Set default value to {}", attributeDefinition.getName(), EMPTY_VALUE);
286             attributeDefinition.set_default(EMPTY_VALUE);
287         } else if (!isEmptyValue(value)) {
288             attributeDefinition.set_default(converter.convert(value, innerType, dataTypes));
289         }
290         return StorageOperationStatus.OK;
291     }
292
293     private StorageOperationStatus validateAndUpdateAttributeComplexValue(final AttributeDataDefinition attributeDefinition,
294                                                                           final String attributeType, final String value,
295                                                                           final DataTypeDefinition dataTypeDefinition,
296                                                                           final Map<String, DataTypeDefinition> dataTypes) {
297         final ImmutablePair<JsonElement, Boolean> validateResult = dataTypeValidatorConverter.validateAndUpdate(value, dataTypeDefinition, dataTypes);
298         if (!validateResult.right.booleanValue()) {
299             log.debug(THE_VALUE_OF_ATTRIBUTE_FROM_TYPE_IS_INVALID, attributeType, attributeType);
300             return StorageOperationStatus.INVALID_VALUE;
301         }
302         final JsonElement jsonElement = validateResult.left;
303         log.trace("Going to update value in attribute definition {} {}", attributeDefinition.getName(), (jsonElement != null ? jsonElement.toString() : null));
304         updateAttributeValue(attributeDefinition, jsonElement);
305         return StorageOperationStatus.OK;
306     }
307
308     private void updateAttributeValue(final AttributeDataDefinition attributeDefinition, final JsonElement jsonElement) {
309         attributeDefinition.set_default(jsonElement);
310     }
311
312     public Either<Object, Boolean> validateAndUpdateAttributeValue(final AttributeDataDefinition attribute,
313                                                                    final String innerType,
314                                                                    final Map<String, DataTypeDefinition> dataTypes) {
315         final var attributeType = attribute.getType();
316         final var value = attribute.getValue();
317         log.trace("Going to validate attribute value and its type. type = {}, value = {}", attributeType, value);
318         final var type = getType(attributeType);
319         if (type == null) {
320             final var dataTypeDefinition = dataTypes.get(attributeType);
321             final var validateResult = dataTypeValidatorConverter.validateAndUpdate(value, dataTypeDefinition, dataTypes);
322             if (Boolean.FALSE.equals(validateResult.right)) {
323                 log.debug(THE_VALUE_OF_ATTRIBUTE_FROM_TYPE_IS_INVALID, value, attributeType);
324                 return Either.right(false);
325             }
326             return Either.left(getValueFromJsonElement(validateResult.left));
327         }
328         log.trace("before validating property type {}", attributeType);
329         if (!isValidValue(type, value, innerType, dataTypes)) {
330             log.debug(THE_VALUE_OF_ATTRIBUTE_FROM_TYPE_IS_INVALID, value, type);
331             return Either.right(false);
332         }
333         Object convertedValue = value;
334         if (!isEmptyValue(value)) {
335             convertedValue = type.getConverter().convert(value, innerType, dataTypes);
336         }
337         return Either.left(convertedValue);
338     }
339
340 }