Support updated data types in service import
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / impl / DataTypeImportManager.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.sdc.be.components.impl;
22
23 import fj.data.Either;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 import javax.annotation.Resource;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.commons.lang3.tuple.ImmutablePair;
36 import org.openecomp.sdc.be.dao.api.ActionStatus;
37 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
38 import org.openecomp.sdc.be.impl.ComponentsUtils;
39 import org.openecomp.sdc.be.model.DataTypeDefinition;
40 import org.openecomp.sdc.be.model.Model;
41 import org.openecomp.sdc.be.model.PropertyDefinition;
42 import org.openecomp.sdc.be.model.normatives.ElementTypeEnum;
43 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
44 import org.openecomp.sdc.be.model.operations.impl.ModelOperation;
45 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
46 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
47 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
48 import org.openecomp.sdc.be.utils.TypeUtils;
49 import org.openecomp.sdc.common.log.wrappers.Logger;
50 import org.openecomp.sdc.exception.ResponseFormat;
51 import org.springframework.stereotype.Component;
52
53 @Component("dataTypeImportManager")
54 public class DataTypeImportManager {
55
56     private static final Logger log = Logger.getLogger(DataTypeImportManager.class.getName());
57     @Resource
58     private PropertyOperation propertyOperation;
59     @Resource
60     private ComponentsUtils componentsUtils;
61     @Resource
62     private CommonImportManager commonImportManager;
63     @Resource
64     private ModelOperation modelOperation;
65
66     public Either<List<ImmutablePair<DataTypeDefinition, Boolean>>, ResponseFormat> createDataTypes(final String dataTypeYml, final String modelName,
67                                                                                                     final boolean includeToModelDefaultImports) {
68         final Either<List<ImmutablePair<DataTypeDefinition, Boolean>>, ResponseFormat> elementTypes = commonImportManager.createElementTypes(
69             dataTypeYml, dataTypesFromYml -> createDataTypesFromYml(dataTypeYml, modelName), this::createDataTypesByDao, ElementTypeEnum.DATA_TYPE);
70
71         if (includeToModelDefaultImports && StringUtils.isNotEmpty(modelName)) {
72             commonImportManager.addTypesToDefaultImports(ElementTypeEnum.DATA_TYPE, dataTypeYml, modelName);
73         }
74         if (!includeToModelDefaultImports && StringUtils.isNotEmpty(modelName) && elementTypes.isLeft()) {
75             commonImportManager.updateTypesInAdditionalTypesImport(ElementTypeEnum.DATA_TYPE, dataTypeYml, modelName);
76         }
77         return elementTypes;
78     }
79
80     private Either<List<DataTypeDefinition>, ActionStatus> createDataTypesFromYml(final String dataTypesYml, final String modelName) {
81         final Either<List<DataTypeDefinition>, ActionStatus> dataTypesEither = commonImportManager.createElementTypesFromYml(dataTypesYml,
82             this::createDataType);
83         if (dataTypesEither.isRight()) {
84             return dataTypesEither;
85         }
86         final List<DataTypeDefinition> dataTypes = dataTypesEither.left().value();
87         if (StringUtils.isNotEmpty(modelName)) {
88             final Optional<Model> modelOptional = modelOperation.findModelByName(modelName);
89             if (modelOptional.isPresent()) {
90                 dataTypes.forEach(dataType -> dataType.setModel(modelName));
91             } else {
92                 return Either.right(ActionStatus.INVALID_MODEL);
93             }
94         }
95         if (log.isTraceEnabled()) {
96             log.trace("Unsorted datatypes order:");
97             dataTypes.stream().forEach(dt -> log.trace(dt.getName()));
98         }
99
100         long startTime = System.currentTimeMillis();
101         List<DataTypeDefinition> sortedDataTypeDefinitions = sortDataTypesByDependencyOrder(dataTypes);
102
103         if (log.isTraceEnabled()) {
104             long sortTime = System.currentTimeMillis() - startTime;
105             log.trace("Sorting " + sortedDataTypeDefinitions.size() + " data types from model: " + modelName + " took: " + sortTime);
106             log.trace("Sorted datatypes order:");
107             sortedDataTypeDefinitions.stream().forEach(dt -> log.trace(dt.getName()));
108         }
109         return Either.left(sortedDataTypeDefinitions);
110     }
111     
112     private List<DataTypeDefinition> sortDataTypesByDependencyOrder(final List<DataTypeDefinition> dataTypes) {
113         final List<DataTypeDefinition> sortedDataTypeDefinitions = new ArrayList<>();
114         final Map<String, DataTypeDefinition> dataTypeDefinitionsMap = new HashMap<>();
115         
116         dataTypes.forEach(dataType -> {
117             
118             int highestDependencyIndex = -1;
119             for (final String dependencyName : getDependencyTypes(dataType, dataTypes)) {
120                 final DataTypeDefinition dependency = dataTypeDefinitionsMap.get(dependencyName);
121                 final int indexOfDependency = sortedDataTypeDefinitions.lastIndexOf(dependency);
122                 highestDependencyIndex = indexOfDependency > highestDependencyIndex ? indexOfDependency : highestDependencyIndex;
123             }
124             sortedDataTypeDefinitions.add(highestDependencyIndex + 1, dataType);
125             dataTypeDefinitionsMap.put(dataType.getName(), dataType);
126        
127             } );
128         
129         return sortedDataTypeDefinitions;
130     }
131     
132     private Collection<String> getDependencyTypes(final DataTypeDefinition dataType, final List<DataTypeDefinition> dataTypes) {
133         final Set<String> dependencies = new HashSet<>();
134         if (dataType.getDerivedFromName() != null) {
135             dependencies.add(dataType.getDerivedFromName());
136         }
137         if (dataType.getProperties() != null) {
138             dataType.getProperties().stream().forEach(property -> dependencies.add(property.getType()));
139         }
140         dataTypes.stream().filter(dependencyCandidate -> dependencies.contains(dependencyCandidate.getName()))
141                 .forEach(dependencyDataType -> dependencies.addAll(getDependencyTypes(dependencyDataType, dataTypes)));
142         return dependencies;
143     }
144
145     private Either<List<ImmutablePair<DataTypeDefinition, Boolean>>, ResponseFormat> createDataTypesByDao(
146         List<DataTypeDefinition> dataTypesToCreate) {
147         return commonImportManager.createElementTypesByDao(dataTypesToCreate, this::validateDataType,
148             dataType -> new ImmutablePair<>(ElementTypeEnum.DATA_TYPE, UniqueIdBuilder.buildDataTypeUid(dataType.getModel(), dataType.getName())),
149             dataTypeUid -> propertyOperation.getDataTypeByUidWithoutDerived(dataTypeUid, true),
150             dataType -> propertyOperation.addDataType(dataType),
151             (newDataType, oldDataType) -> propertyOperation.updateDataType(newDataType, oldDataType));
152     }
153
154     private Either<ActionStatus, ResponseFormat> validateDataType(DataTypeDefinition dataType) {
155         String dataTypeName = dataType.getName();
156         List<PropertyDefinition> properties = dataType.getProperties();
157         if (properties == null) {
158             // At least one parameter should be defined either in the properties
159
160             // section or at one of the parents
161             String derivedDataType = dataType.getDerivedFromName();
162             // If there are no properties, then we can create a data type if it
163
164             // is an abstract one or it derives from non abstract data type
165             if (derivedDataType == null || derivedDataType.isEmpty()) {
166                 if (!isAbstract(dataType.getName()) && !ToscaPropertyType.isScalarType(dataTypeName)) {
167                     log.debug("Data type {} must have properties unless it derives from non abstract data type", dataType.getName());
168                     ResponseFormat responseFormat = componentsUtils
169                         .getResponseFormatByDataType(ActionStatus.DATA_TYPE_NOR_PROPERTIES_NEITHER_DERIVED_FROM, dataType, null);
170                     return Either.right(responseFormat);
171                 }
172             } else {
173                 if (!ToscaPropertyType.isScalarType(dataTypeName) && isAbstract(derivedDataType)) {
174                     log.warn("Creating data type {} which derived from abstract data type with no properties", dataType.getName());
175                 }
176             }
177         } else {
178             // properties tag cannot be empty
179             if (properties.isEmpty()) {
180                 ResponseFormat responseFormat = componentsUtils
181                     .getResponseFormatByDataType(ActionStatus.DATA_TYPE_PROPERTIES_CANNOT_BE_EMPTY, dataType, null);
182                 return Either.right(responseFormat);
183             }
184             // check no duplicates
185             Set<String> collect = properties.stream().map(PropertyDataDefinition::getName).collect(Collectors.toSet());
186             if (collect != null && properties.size() != collect.size()) {
187                 ResponseFormat responseFormat = componentsUtils
188                     .getResponseFormatByDataType(ActionStatus.DATA_TYPE_DUPLICATE_PROPERTY, dataType, null);
189                 return Either.right(responseFormat);
190             }
191             List<String> propertiesWithSameTypeAsDataType = properties.stream().filter(p -> p.getType().equals(dataType.getName()))
192                 .map(PropertyDataDefinition::getName).collect(Collectors.toList());
193             if (propertiesWithSameTypeAsDataType != null && !propertiesWithSameTypeAsDataType.isEmpty()) {
194                 log.debug("The data type {} contains properties with the type {}", dataType.getName(), dataType.getName());
195                 ResponseFormat responseFormat = componentsUtils
196                     .getResponseFormatByDataType(ActionStatus.DATA_TYPE_PROEPRTY_CANNOT_HAVE_SAME_TYPE_OF_DATA_TYPE, dataType,
197                         propertiesWithSameTypeAsDataType);
198                 return Either.right(responseFormat);
199             }
200         }
201         String derivedDataType = dataType.getDerivedFromName();
202         if (derivedDataType != null) {
203             Either<DataTypeDefinition, StorageOperationStatus> derivedDataTypeByName = propertyOperation.getDataTypeByName(derivedDataType,
204                 dataType.getModel());
205             if (derivedDataTypeByName.isRight()) {
206                 StorageOperationStatus status = derivedDataTypeByName.right().value();
207                 if (status == StorageOperationStatus.NOT_FOUND) {
208                     ResponseFormat responseFormat = componentsUtils
209                         .getResponseFormatByDataType(ActionStatus.DATA_TYPE_DERIVED_IS_MISSING, dataType, null);
210                     return Either.right(responseFormat);
211                 } else {
212                     ResponseFormat responseFormat = componentsUtils.getResponseFormatByDataType(ActionStatus.GENERAL_ERROR, dataType, null);
213                     return Either.right(responseFormat);
214                 }
215             } else {
216                 DataTypeDefinition derivedDataTypeDef = derivedDataTypeByName.left().value();
217                 if (properties != null && !properties.isEmpty() && derivedDataTypeDef != null) {
218                     if (isScalarType(derivedDataTypeDef)) {
219                         ResponseFormat responseFormat = componentsUtils
220                             .getResponseFormatByDataType(ActionStatus.DATA_TYPE_CANNOT_HAVE_PROPERTIES, dataType, null);
221                         return Either.right(responseFormat);
222                     }
223                     Set<String> allParentsProps = new HashSet<>();
224                     do {
225                         List<PropertyDefinition> currentParentsProps = derivedDataTypeDef.getProperties();
226                         if (currentParentsProps != null) {
227                             for (PropertyDefinition propertyDefinition : currentParentsProps) {
228                                 allParentsProps.add(propertyDefinition.getName());
229                             }
230                         }
231                         derivedDataTypeDef = derivedDataTypeDef.getDerivedFrom();
232                     } while (derivedDataTypeDef != null);
233                     // Check that no property is already defined in one of the
234
235                     // ancestors
236                     Set<String> alreadyExistPropsCollection = properties.stream().filter(p -> allParentsProps.contains(p.getName()))
237                         .map(PropertyDataDefinition::getName).collect(Collectors.toSet());
238                     if (alreadyExistPropsCollection != null && !alreadyExistPropsCollection.isEmpty()) {
239                         List<String> duplicateProps = new ArrayList<>();
240                         duplicateProps.addAll(alreadyExistPropsCollection);
241                         ResponseFormat responseFormat = componentsUtils
242                             .getResponseFormatByDataType(ActionStatus.DATA_TYPE_PROPERTY_ALREADY_DEFINED_IN_ANCESTOR, dataType, duplicateProps);
243                         return Either.right(responseFormat);
244                     }
245                 }
246             }
247         }
248         return Either.left(ActionStatus.OK);
249     }
250
251     private boolean isAbstract(String dataTypeName) {
252         ToscaPropertyType isPrimitiveToscaType = ToscaPropertyType.isValidType(dataTypeName);
253         return isPrimitiveToscaType != null && isPrimitiveToscaType.isAbstract();
254     }
255
256     private boolean isScalarType(DataTypeDefinition dataTypeDef) {
257         boolean isScalar = false;
258         DataTypeDefinition dataType = dataTypeDef;
259         while (dataType != null) {
260             String name = dataType.getName();
261             if (ToscaPropertyType.isScalarType(name)) {
262                 isScalar = true;
263                 break;
264             }
265             dataType = dataType.getDerivedFrom();
266         }
267         return isScalar;
268     }
269
270     private DataTypeDefinition createDataType(String dataTypeName, Map<String, Object> toscaJson) {
271         DataTypeDefinition dataType = new DataTypeDefinition();
272         dataType.setName(dataTypeName);
273         if (toscaJson != null) {
274             // Description
275             commonImportManager.setField(toscaJson, TypeUtils.ToscaTagNamesEnum.DESCRIPTION.getElementName(), dataType::setDescription);
276             // Derived From
277             commonImportManager.setField(toscaJson, TypeUtils.ToscaTagNamesEnum.DERIVED_FROM.getElementName(), dataType::setDerivedFromName);
278             // Properties
279             CommonImportManager.setProperties(toscaJson, dataType::setProperties);
280         }
281         return dataType;
282     }
283 }