01e5cdcd400f1283f5eaa13516d96bd7ef50aa0e
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / operations / impl / ModelOperation.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  ================================================================================
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *        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.
15  *
16  *  SPDX-License-Identifier: Apache-2.0
17  *  ============LICENSE_END=========================================================
18  */
19 package org.openecomp.sdc.be.model.operations.impl;
20
21 import static org.openecomp.sdc.common.api.Constants.ADDITIONAL_TYPE_DEFINITIONS;
22
23 import fj.data.Either;
24 import java.nio.charset.StandardCharsets;
25 import java.nio.file.Path;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.EnumMap;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Optional;
34 import java.util.stream.Collectors;
35 import org.apache.commons.collections.MapUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.lang3.tuple.ImmutablePair;
38 import org.onap.sdc.tosca.services.YamlUtil;
39 import org.openecomp.sdc.be.dao.api.ActionStatus;
40 import org.openecomp.sdc.be.dao.cassandra.ToscaModelImportCassandraDao;
41 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
42 import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation;
43 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
44 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao;
45 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
46 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
47 import org.openecomp.sdc.be.dao.jsongraph.types.VertexTypeEnum;
48 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
49 import org.openecomp.sdc.be.data.model.ToscaImportByModel;
50 import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum;
51 import org.openecomp.sdc.be.datatypes.enums.ModelTypeEnum;
52 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
53 import org.openecomp.sdc.be.model.Model;
54 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier;
55 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
56 import org.openecomp.sdc.be.model.normatives.ElementTypeEnum;
57 import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation;
58 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
59 import org.openecomp.sdc.be.resources.data.ModelData;
60 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
61 import org.openecomp.sdc.common.log.wrappers.Logger;
62 import org.springframework.beans.factory.annotation.Autowired;
63 import org.springframework.stereotype.Component;
64 import org.yaml.snakeyaml.Yaml;
65
66 @Component("model-operation")
67 public class ModelOperation {
68
69     private static final Logger log = Logger.getLogger(ModelOperation.class);
70     static final Path ADDITIONAL_TYPE_DEFINITIONS_PATH = Path.of(ADDITIONAL_TYPE_DEFINITIONS);
71
72     private final JanusGraphGenericDao janusGraphGenericDao;
73     private final JanusGraphDao janusGraphDao;
74     private final ToscaModelImportCassandraDao toscaModelImportCassandraDao;
75     private final DerivedFromOperation derivedFromOperation;
76     private ModelElementOperation modelElementOperation;
77
78     @Autowired
79     public ModelOperation(final JanusGraphGenericDao janusGraphGenericDao,
80                           final JanusGraphDao janusGraphDao,
81                           final ToscaModelImportCassandraDao toscaModelImportCassandraDao,
82                           final DerivedFromOperation derivedFromOperation) {
83         this.janusGraphGenericDao = janusGraphGenericDao;
84         this.janusGraphDao = janusGraphDao;
85         this.toscaModelImportCassandraDao = toscaModelImportCassandraDao;
86         this.derivedFromOperation = derivedFromOperation;
87     }
88
89     public Model createModel(final Model model, final boolean inTransaction) {
90         Model result = null;
91         final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
92         try {
93             final Either<ModelData, JanusGraphOperationStatus> createNode = janusGraphGenericDao.createNode(modelData, ModelData.class);
94             if (createNode.isRight()) {
95                 final var janusGraphOperationStatus = createNode.right().value();
96                 log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(), "Problem while creating model, reason {}",
97                     janusGraphOperationStatus);
98                 if (janusGraphOperationStatus == JanusGraphOperationStatus.JANUSGRAPH_SCHEMA_VIOLATION) {
99                     throw ModelOperationExceptionSupplier.modelAlreadyExists(model.getName()).get();
100                 }
101                 throw new OperationException(ActionStatus.GENERAL_ERROR,
102                     String.format("Failed to create model %s on JanusGraph with %s error", model, janusGraphOperationStatus));
103             }
104             addDerivedFromRelation(model);
105             result = new Model(createNode.left().value().getName(), model.getDerivedFrom(), model.getModelType());
106             return result;
107         } finally {
108             if (!inTransaction) {
109                 if (Objects.nonNull(result)) {
110                     janusGraphGenericDao.commit();
111                 } else {
112                     janusGraphGenericDao.rollback();
113                 }
114             }
115         }
116     }
117
118     private void addDerivedFromRelation(final Model model) {
119         final String derivedFrom = model.getDerivedFrom();
120         if (derivedFrom == null) {
121             return;
122         }
123         log.debug("Adding derived from relation between model {} to its parent {}",
124             model.getName(), derivedFrom);
125         final Optional<Model> derivedFromModelOptional = this.findModelByName(derivedFrom);
126         if (derivedFromModelOptional.isPresent()) {
127             final Either<GraphRelation, StorageOperationStatus> result = derivedFromOperation.addDerivedFromRelation(
128                 UniqueIdBuilder.buildModelUid(model.getName()),
129                 UniqueIdBuilder.buildModelUid(derivedFromModelOptional.get().getName()), NodeTypeEnum.Model);
130             if (result.isRight()) {
131                 throw new OperationException(ActionStatus.GENERAL_ERROR,
132                     String.format("Failed to create relationship from model %s to derived from model %s on JanusGraph with %s error", model,
133                         derivedFrom, result.right().value()));
134             }
135         }
136     }
137
138     public Optional<GraphVertex> findModelVertexByName(final String name) {
139         if (StringUtils.isEmpty(name)) {
140             return Optional.empty();
141         }
142         final Map<GraphPropertyEnum, Object> props = new EnumMap<>(GraphPropertyEnum.class);
143         props.put(GraphPropertyEnum.NAME, name);
144         props.put(GraphPropertyEnum.UNIQUE_ID, UniqueIdBuilder.buildModelUid(name));
145         final List<GraphVertex> modelVerticesList = findModelVerticesByCriteria(props);
146         if (modelVerticesList.isEmpty()) {
147             return Optional.empty();
148         }
149         return Optional.ofNullable(modelVerticesList.get(0));
150     }
151
152     public Optional<Model> findModelByName(final String name) {
153         if (StringUtils.isEmpty(name)) {
154             return Optional.empty();
155         }
156         final Optional<GraphVertex> modelVertexOpt = findModelVertexByName(name);
157         if (modelVertexOpt.isEmpty()) {
158             return Optional.empty();
159         }
160
161         final GraphVertex graphVertex = modelVertexOpt.get();
162         return Optional.of(convertToModel(graphVertex));
163     }
164
165     public void createModelImports(final String modelId, final Map<String, byte[]> zipContent) {
166         if (MapUtils.isEmpty(zipContent)) {
167             return;
168         }
169         final List<ToscaImportByModel> toscaImportByModelList = zipContent.entrySet().stream()
170             .map(entry -> {
171                 final String path = entry.getKey();
172                 final byte[] bytes = entry.getValue();
173                 final String content = new String(bytes, StandardCharsets.UTF_8);
174                 final var toscaImportByModel = new ToscaImportByModel();
175                 toscaImportByModel.setModelId(modelId);
176                 toscaImportByModel.setFullPath(path);
177                 toscaImportByModel.setContent(content);
178                 return toscaImportByModel;
179             }).collect(Collectors.toList());
180         toscaModelImportCassandraDao.replaceImports(modelId, toscaImportByModelList);
181     }
182
183     /**
184      * Find all the model default imports, with the option to include the default imports from the parent model.
185      *
186      * @param modelId       the model id
187      * @param includeParent a flag to include the parent model imports.
188      * @return the list of model default imports, or an empty list if no imports were found.
189      */
190     public List<ToscaImportByModel> findAllModelImports(final String modelId, final boolean includeParent) {
191         final List<ToscaImportByModel> toscaImportByModelList = toscaModelImportCassandraDao.findAllByModel(modelId);
192         if (includeParent) {
193             findModelByName(modelId).ifPresent(model -> {
194                 if (model.getDerivedFrom() != null) {
195                     toscaImportByModelList.addAll(toscaModelImportCassandraDao.findAllByModel(model.getDerivedFrom()));
196                 }
197             });
198         }
199         toscaImportByModelList.sort((o1, o2) -> {
200             final int modelIdComparison = o1.getModelId().compareTo(o2.getModelId());
201             if (modelIdComparison == 0) {
202                 return o1.getFullPath().compareTo(o2.getFullPath());
203             }
204             return modelIdComparison;
205         });
206         return toscaImportByModelList;
207     }
208
209     /**
210      * Finds all the models.
211      *
212      * @return the list of models
213      */
214     public List<Model> findAllModels() {
215         return findModelsByCriteria(Collections.emptyMap());
216     }
217     
218     public List<Model> findModels(final ModelTypeEnum modelType) {
219         final Map<GraphPropertyEnum, Object> propertyCriteria = new EnumMap<>(GraphPropertyEnum.class);
220         propertyCriteria.put(GraphPropertyEnum.MODEL_TYPE, modelType.getValue());
221         
222         return findModelsByCriteria(propertyCriteria);
223     }
224
225     private List<Model> findModelsByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
226         final List<GraphVertex> modelVerticesByCriteria = findModelVerticesByCriteria(propertyCriteria);
227         if (modelVerticesByCriteria.isEmpty()) {
228             return Collections.emptyList();
229         }
230
231         return modelVerticesByCriteria.stream().map(this::convertToModel).collect(Collectors.toList());
232     }
233
234     private List<GraphVertex> findModelVerticesByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
235         final Either<List<GraphVertex>, JanusGraphOperationStatus> result = janusGraphDao.getByCriteria(VertexTypeEnum.MODEL, propertyCriteria);
236         if (result.isRight()) {
237             final var janusGraphOperationStatus = result.right().value();
238             if (janusGraphOperationStatus == JanusGraphOperationStatus.NOT_FOUND) {
239                 return Collections.emptyList();
240             }
241             final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
242             log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
243             throw operationException;
244         }
245         return result.left().value();
246     }
247
248     private Model convertToModel(final GraphVertex modelGraphVertex) {
249         final String modelName = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME);
250         final String modelTypeProperty = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.MODEL_TYPE);
251         ModelTypeEnum modelType = ModelTypeEnum.NORMATIVE;
252         final Optional<ModelTypeEnum> optionalModelTypeEnum = ModelTypeEnum.findByValue(modelTypeProperty);
253         if (optionalModelTypeEnum.isPresent()) {
254             modelType = optionalModelTypeEnum.get();
255         }
256         
257         final Either<ImmutablePair<ModelData, GraphEdge>, JanusGraphOperationStatus> parentNode =
258             janusGraphGenericDao.getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Model), UniqueIdBuilder.buildModelUid(modelName),
259                 GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, ModelData.class);
260         log.debug("After retrieving DERIVED_FROM node of {}. status is {}", modelName, parentNode);
261         if (parentNode.isRight()) {
262             final JanusGraphOperationStatus janusGraphOperationStatus = parentNode.right().value();
263             if (janusGraphOperationStatus != JanusGraphOperationStatus.NOT_FOUND) {
264                 final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
265                 log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
266                 throw operationException;
267             }
268             return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), modelType);
269         } else {
270             final ModelData parentModel = parentNode.left().value().getKey();
271             return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName(), modelType);
272         }
273     }
274
275     public void addTypesToDefaultImports(final ElementTypeEnum elementTypeEnum, final String typesYaml, final String modelName) {
276         final List<ToscaImportByModel> modelImportList = toscaModelImportCassandraDao.findAllByModel(modelName);
277         final Optional<ToscaImportByModel> additionalTypeDefinitionsImportOptional = modelImportList.stream()
278             .filter(t -> ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(t.getFullPath()))).findAny();
279         final ToscaImportByModel additionalTypeDefinitionsImport;
280         final List<ToscaImportByModel> rebuiltModelImportList;
281         if (additionalTypeDefinitionsImportOptional.isPresent()) {
282             additionalTypeDefinitionsImport = additionalTypeDefinitionsImportOptional.get();
283             rebuiltModelImportList = modelImportList.stream()
284                 .filter(toscaImportByModel -> !ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(toscaImportByModel.getFullPath())))
285                 .collect(Collectors.toList());
286         } else {
287             additionalTypeDefinitionsImport = new ToscaImportByModel();
288             additionalTypeDefinitionsImport.setModelId(modelName);
289             additionalTypeDefinitionsImport.setFullPath(ADDITIONAL_TYPE_DEFINITIONS_PATH.toString());
290             additionalTypeDefinitionsImport.setContent(createAdditionalTypeDefinitionsHeader());
291             rebuiltModelImportList = new ArrayList<>(modelImportList);
292         }
293
294         final Map<String, Object> typesYamlMap = new Yaml().load(typesYaml);
295         removeExistingTypesFromDefaultImports(elementTypeEnum, typesYamlMap, rebuiltModelImportList);
296
297         final Map<String, Object> originalContent = new Yaml().load(additionalTypeDefinitionsImport.getContent());
298         additionalTypeDefinitionsImport.setContent(buildAdditionalTypeDefinitionsContent(elementTypeEnum, typesYamlMap, originalContent));
299         rebuiltModelImportList.add(additionalTypeDefinitionsImport);
300
301         toscaModelImportCassandraDao.saveAll(modelName, rebuiltModelImportList);
302     }
303
304     private void removeExistingTypesFromDefaultImports(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYaml,
305                                                        final List<ToscaImportByModel> defaultImportList) {
306         defaultImportList.forEach(toscaImportByModel -> {
307             final Map<String, Object> existingImportYamlMap = new Yaml().load(toscaImportByModel.getContent());
308             final Map<String, Object> currentTypeYamlMap = (Map<String, Object>) existingImportYamlMap.get(elementTypeEnum.getToscaEntryName());
309             if (MapUtils.isNotEmpty(currentTypeYamlMap)) {
310                 typesYaml.keySet().forEach(currentTypeYamlMap::remove);
311             }
312             toscaImportByModel.setContent(new YamlUtil().objectToYaml(existingImportYamlMap));
313         });
314     }
315
316     private String buildAdditionalTypeDefinitionsContent(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYamlMap,
317                                                          final Map<String, Object> originalContent) {
318         final Map<String, Object> originalTypeContent = (Map<String, Object>) originalContent.get(elementTypeEnum.getToscaEntryName());
319         if (MapUtils.isEmpty(originalTypeContent)) {
320             originalContent.put(elementTypeEnum.getToscaEntryName(), new LinkedHashMap<>(typesYamlMap));
321         } else {
322             originalTypeContent.putAll(typesYamlMap);
323         }
324         return new YamlUtil().objectToYaml(originalContent);
325     }
326
327     private String createAdditionalTypeDefinitionsHeader() {
328         return "tosca_definitions_version: tosca_simple_yaml_1_3" + "\n"
329             + "description: Auto-generated file that contains package custom types or types added after system installation." + "\n";
330     }
331
332     /**
333      * Deletes the given model if it exists, along with its MODEL_ELEMENT edges and import files.
334      *
335      * @param model         the model
336      * @param inTransaction if the operation is called in the middle of a janusgraph transaction
337      */
338     public void deleteModel(final Model model, final boolean inTransaction) {
339         boolean rollback = false;
340
341         try {
342             final GraphVertex modelVertexByName = findModelVertexByName(model.getName()).orElse(null);
343             if (modelVertexByName == null) {
344                 return;
345             }
346             toscaModelImportCassandraDao.deleteAllByModel(model.getName());
347             modelElementOperation.deleteModelElements(model, inTransaction);
348             deleteModel(model);
349         } catch (final OperationException e) {
350             rollback = true;
351             throw e;
352         } catch (final Exception e) {
353             rollback = true;
354             throw new OperationException(e, ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
355         } finally {
356             if (!inTransaction) {
357                 if (rollback) {
358                     janusGraphGenericDao.rollback();
359                 } else {
360                     janusGraphGenericDao.commit();
361                 }
362             }
363         }
364     }
365
366     private void deleteModel(final Model model) {
367         final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
368         final Either<ModelData, JanusGraphOperationStatus> deleteParentNodeByModel = janusGraphGenericDao.deleteNode(modelData, ModelData.class);
369         if (deleteParentNodeByModel.isRight()) {
370             final var janusGraphOperationStatus = deleteParentNodeByModel.right().value();
371             log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(),
372                 "Failed to delete model {} on JanusGraph with status {}", new Object[] {model.getName(), janusGraphOperationStatus});
373             throw new OperationException(ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
374         }
375     }
376
377     @Autowired
378     public void setModelElementOperation(final ModelElementOperation modelElementOperation) {
379         this.modelElementOperation = modelElementOperation;
380     }
381
382 }