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
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.
16 * SPDX-License-Identifier: Apache-2.0
17 * ============LICENSE_END=========================================================
19 package org.openecomp.sdc.be.model.operations.impl;
21 import fj.data.Either;
22 import java.nio.charset.StandardCharsets;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.EnumMap;
27 import java.util.LinkedHashMap;
28 import java.util.List;
30 import java.util.Objects;
31 import java.util.Optional;
32 import java.util.stream.Collectors;
33 import org.apache.commons.collections.MapUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.commons.lang3.tuple.ImmutablePair;
36 import org.onap.sdc.tosca.services.YamlUtil;
37 import org.openecomp.sdc.be.dao.api.ActionStatus;
38 import org.openecomp.sdc.be.dao.cassandra.ToscaModelImportCassandraDao;
39 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
40 import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation;
41 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
42 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao;
43 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
44 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
45 import org.openecomp.sdc.be.dao.jsongraph.types.VertexTypeEnum;
46 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
47 import org.openecomp.sdc.be.data.model.ToscaImportByModel;
48 import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum;
49 import org.openecomp.sdc.be.datatypes.enums.ModelTypeEnum;
50 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
51 import org.openecomp.sdc.be.model.Model;
52 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier;
53 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
54 import org.openecomp.sdc.be.model.normatives.ElementTypeEnum;
55 import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation;
56 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
57 import org.openecomp.sdc.be.resources.data.ModelData;
58 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
59 import org.openecomp.sdc.common.log.wrappers.Logger;
60 import org.springframework.beans.factory.annotation.Autowired;
61 import org.springframework.stereotype.Component;
62 import org.yaml.snakeyaml.Yaml;
64 @Component("model-operation")
65 public class ModelOperation {
67 private static final Logger log = Logger.getLogger(ModelOperation.class);
68 static final Path ADDITIONAL_TYPE_DEFINITIONS_PATH = Path.of("additional_type_definitions.yaml");
70 private final JanusGraphGenericDao janusGraphGenericDao;
71 private final JanusGraphDao janusGraphDao;
72 private final ToscaModelImportCassandraDao toscaModelImportCassandraDao;
73 private final DerivedFromOperation derivedFromOperation;
74 private ModelElementOperation modelElementOperation;
77 public ModelOperation(final JanusGraphGenericDao janusGraphGenericDao,
78 final JanusGraphDao janusGraphDao,
79 final ToscaModelImportCassandraDao toscaModelImportCassandraDao,
80 final DerivedFromOperation derivedFromOperation) {
81 this.janusGraphGenericDao = janusGraphGenericDao;
82 this.janusGraphDao = janusGraphDao;
83 this.toscaModelImportCassandraDao = toscaModelImportCassandraDao;
84 this.derivedFromOperation = derivedFromOperation;
87 public Model createModel(final Model model, final boolean inTransaction) {
89 final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
91 final Either<ModelData, JanusGraphOperationStatus> createNode = janusGraphGenericDao.createNode(modelData, ModelData.class);
92 if (createNode.isRight()) {
93 final var janusGraphOperationStatus = createNode.right().value();
94 log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(), "Problem while creating model, reason {}",
95 janusGraphOperationStatus);
96 if (janusGraphOperationStatus == JanusGraphOperationStatus.JANUSGRAPH_SCHEMA_VIOLATION) {
97 throw ModelOperationExceptionSupplier.modelAlreadyExists(model.getName()).get();
99 throw new OperationException(ActionStatus.GENERAL_ERROR,
100 String.format("Failed to create model %s on JanusGraph with %s error", model, janusGraphOperationStatus));
102 addDerivedFromRelation(model);
103 result = new Model(createNode.left().value().getName(), model.getDerivedFrom(), model.getModelType());
106 if (!inTransaction) {
107 if (Objects.nonNull(result)) {
108 janusGraphGenericDao.commit();
110 janusGraphGenericDao.rollback();
116 private void addDerivedFromRelation(final Model model) {
117 final String derivedFrom = model.getDerivedFrom();
118 if (derivedFrom == null) {
121 log.debug("Adding derived from relation between model {} to its parent {}",
122 model.getName(), derivedFrom);
123 final Optional<Model> derivedFromModelOptional = this.findModelByName(derivedFrom);
124 if (derivedFromModelOptional.isPresent()) {
125 final Either<GraphRelation, StorageOperationStatus> result = derivedFromOperation.addDerivedFromRelation(
126 UniqueIdBuilder.buildModelUid(model.getName()),
127 UniqueIdBuilder.buildModelUid(derivedFromModelOptional.get().getName()), NodeTypeEnum.Model);
128 if (result.isRight()) {
129 throw new OperationException(ActionStatus.GENERAL_ERROR,
130 String.format("Failed to create relationship from model %s to derived from model %s on JanusGraph with %s error", model,
131 derivedFrom, result.right().value()));
136 public Optional<GraphVertex> findModelVertexByName(final String name) {
137 if (StringUtils.isEmpty(name)) {
138 return Optional.empty();
140 final Map<GraphPropertyEnum, Object> props = new EnumMap<>(GraphPropertyEnum.class);
141 props.put(GraphPropertyEnum.NAME, name);
142 props.put(GraphPropertyEnum.UNIQUE_ID, UniqueIdBuilder.buildModelUid(name));
143 final List<GraphVertex> modelVerticesList = findModelVerticesByCriteria(props);
144 if (modelVerticesList.isEmpty()) {
145 return Optional.empty();
147 return Optional.ofNullable(modelVerticesList.get(0));
150 public Optional<Model> findModelByName(final String name) {
151 if (StringUtils.isEmpty(name)) {
152 return Optional.empty();
154 final Optional<GraphVertex> modelVertexOpt = findModelVertexByName(name);
155 if (modelVertexOpt.isEmpty()) {
156 return Optional.empty();
159 final GraphVertex graphVertex = modelVertexOpt.get();
160 return Optional.of(convertToModel(graphVertex));
163 public void createModelImports(final String modelId, final Map<String, byte[]> zipContent) {
164 if (MapUtils.isEmpty(zipContent)) {
167 final List<ToscaImportByModel> toscaImportByModelList = zipContent.entrySet().stream()
169 final String path = entry.getKey();
170 final byte[] bytes = entry.getValue();
171 final String content = new String(bytes, StandardCharsets.UTF_8);
172 final var toscaImportByModel = new ToscaImportByModel();
173 toscaImportByModel.setModelId(modelId);
174 toscaImportByModel.setFullPath(path);
175 toscaImportByModel.setContent(content);
176 return toscaImportByModel;
177 }).collect(Collectors.toList());
178 toscaModelImportCassandraDao.replaceImports(modelId, toscaImportByModelList);
182 * Find all the model default imports, with the option to include the default imports from the parent model.
184 * @param modelId the model id
185 * @param includeParent a flag to include the parent model imports.
186 * @return the list of model default imports, or an empty list if no imports were found.
188 public List<ToscaImportByModel> findAllModelImports(final String modelId, final boolean includeParent) {
189 final List<ToscaImportByModel> toscaImportByModelList = toscaModelImportCassandraDao.findAllByModel(modelId);
191 findModelByName(modelId).ifPresent(model -> {
192 if (model.getDerivedFrom() != null) {
193 toscaImportByModelList.addAll(toscaModelImportCassandraDao.findAllByModel(model.getDerivedFrom()));
197 toscaImportByModelList.sort((o1, o2) -> {
198 final int modelIdComparison = o1.getModelId().compareTo(o2.getModelId());
199 if (modelIdComparison == 0) {
200 return o1.getFullPath().compareTo(o2.getFullPath());
202 return modelIdComparison;
204 return toscaImportByModelList;
208 * Finds all the models.
210 * @return the list of models
212 public List<Model> findAllModels() {
213 return findModelsByCriteria(Collections.emptyMap());
216 public List<Model> findModels(final ModelTypeEnum modelType) {
217 final Map<GraphPropertyEnum, Object> propertyCriteria = new EnumMap<>(GraphPropertyEnum.class);
218 propertyCriteria.put(GraphPropertyEnum.MODEL_TYPE, modelType.getValue());
220 return findModelsByCriteria(propertyCriteria);
223 private List<Model> findModelsByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
224 final List<GraphVertex> modelVerticesByCriteria = findModelVerticesByCriteria(propertyCriteria);
225 if (modelVerticesByCriteria.isEmpty()) {
226 return Collections.emptyList();
229 return modelVerticesByCriteria.stream().map(this::convertToModel).collect(Collectors.toList());
232 private List<GraphVertex> findModelVerticesByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
233 final Either<List<GraphVertex>, JanusGraphOperationStatus> result = janusGraphDao.getByCriteria(VertexTypeEnum.MODEL, propertyCriteria);
234 if (result.isRight()) {
235 final var janusGraphOperationStatus = result.right().value();
236 if (janusGraphOperationStatus == JanusGraphOperationStatus.NOT_FOUND) {
237 return Collections.emptyList();
239 final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
240 log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
241 throw operationException;
243 return result.left().value();
246 private Model convertToModel(final GraphVertex modelGraphVertex) {
247 final String modelName = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME);
248 final String modelTypeProperty = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.MODEL_TYPE);
249 final ModelTypeEnum modelType = StringUtils.isEmpty(modelTypeProperty) ? ModelTypeEnum.NORMATIVE : ModelTypeEnum.findByValue(modelTypeProperty).get();
251 final Either<ImmutablePair<ModelData, GraphEdge>, JanusGraphOperationStatus> parentNode =
252 janusGraphGenericDao.getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Model), UniqueIdBuilder.buildModelUid(modelName),
253 GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, ModelData.class);
254 log.debug("After retrieving DERIVED_FROM node of {}. status is {}", modelName, parentNode);
255 if (parentNode.isRight()) {
256 final JanusGraphOperationStatus janusGraphOperationStatus = parentNode.right().value();
257 if (janusGraphOperationStatus != JanusGraphOperationStatus.NOT_FOUND) {
258 final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
259 log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
260 throw operationException;
262 return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), modelType);
264 final ModelData parentModel = parentNode.left().value().getKey();
265 return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName(), modelType);
269 public void addTypesToDefaultImports(final ElementTypeEnum elementTypeEnum, final String typesYaml, final String modelName) {
270 final List<ToscaImportByModel> modelImportList = toscaModelImportCassandraDao.findAllByModel(modelName);
271 final Optional<ToscaImportByModel> additionalTypeDefinitionsImportOptional = modelImportList.stream()
272 .filter(t -> ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(t.getFullPath()))).findAny();
273 final ToscaImportByModel additionalTypeDefinitionsImport;
274 final List<ToscaImportByModel> rebuiltModelImportList;
275 if (additionalTypeDefinitionsImportOptional.isPresent()) {
276 additionalTypeDefinitionsImport = additionalTypeDefinitionsImportOptional.get();
277 rebuiltModelImportList = modelImportList.stream()
278 .filter(toscaImportByModel -> !ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(toscaImportByModel.getFullPath())))
279 .collect(Collectors.toList());
281 additionalTypeDefinitionsImport = new ToscaImportByModel();
282 additionalTypeDefinitionsImport.setModelId(modelName);
283 additionalTypeDefinitionsImport.setFullPath(ADDITIONAL_TYPE_DEFINITIONS_PATH.toString());
284 additionalTypeDefinitionsImport.setContent(createAdditionalTypeDefinitionsHeader());
285 rebuiltModelImportList = new ArrayList<>(modelImportList);
288 final Map<String, Object> typesYamlMap = new Yaml().load(typesYaml);
289 removeExistingTypesFromDefaultImports(elementTypeEnum, typesYamlMap, rebuiltModelImportList);
291 final Map<String, Object> originalContent = new Yaml().load(additionalTypeDefinitionsImport.getContent());
292 additionalTypeDefinitionsImport.setContent(buildAdditionalTypeDefinitionsContent(elementTypeEnum, typesYamlMap, originalContent));
293 rebuiltModelImportList.add(additionalTypeDefinitionsImport);
295 toscaModelImportCassandraDao.saveAll(modelName, rebuiltModelImportList);
298 private void removeExistingTypesFromDefaultImports(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYaml,
299 final List<ToscaImportByModel> defaultImportList) {
300 defaultImportList.forEach(toscaImportByModel -> {
301 final Map<String, Object> existingImportYamlMap = new Yaml().load(toscaImportByModel.getContent());
302 final Map<String, Object> currentTypeYamlMap = (Map<String, Object>) existingImportYamlMap.get(elementTypeEnum.getToscaEntryName());
303 if (MapUtils.isNotEmpty(currentTypeYamlMap)) {
304 typesYaml.keySet().forEach(currentTypeYamlMap::remove);
306 toscaImportByModel.setContent(new YamlUtil().objectToYaml(existingImportYamlMap));
310 private String buildAdditionalTypeDefinitionsContent(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYamlMap,
311 final Map<String, Object> originalContent) {
312 final Map<String, Object> originalTypeContent = (Map<String, Object>) originalContent.get(elementTypeEnum.getToscaEntryName());
313 if (MapUtils.isEmpty(originalTypeContent)) {
314 originalContent.put(elementTypeEnum.getToscaEntryName(), new LinkedHashMap<>(typesYamlMap));
316 originalTypeContent.putAll(typesYamlMap);
318 return new YamlUtil().objectToYaml(originalContent);
321 private String createAdditionalTypeDefinitionsHeader() {
322 return "tosca_definitions_version: tosca_simple_yaml_1_3" + "\n"
323 + "description: Auto-generated file that contains package custom types or types added after system installation." + "\n";
327 * Deletes the given model if it exists, along with its MODEL_ELEMENT edges and import files.
329 * @param model the model
330 * @param inTransaction if the operation is called in the middle of a janusgraph transaction
332 public void deleteModel(final Model model, final boolean inTransaction) {
333 boolean rollback = false;
336 final GraphVertex modelVertexByName = findModelVertexByName(model.getName()).orElse(null);
337 if (modelVertexByName == null) {
340 toscaModelImportCassandraDao.deleteAllByModel(model.getName());
341 modelElementOperation.deleteModelElements(model, inTransaction);
343 } catch (final OperationException e) {
346 } catch (final Exception e) {
348 throw new OperationException(e, ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
350 if (!inTransaction) {
352 janusGraphGenericDao.rollback();
354 janusGraphGenericDao.commit();
360 private void deleteModel(final Model model) {
361 final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
362 final Either<ModelData, JanusGraphOperationStatus> deleteParentNodeByModel = janusGraphGenericDao.deleteNode(modelData, ModelData.class);
363 if (deleteParentNodeByModel.isRight()) {
364 final var janusGraphOperationStatus = deleteParentNodeByModel.right().value();
365 log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(),
366 "Failed to delete model {} on JanusGraph with status {}", new Object[] {model.getName(), janusGraphOperationStatus});
367 throw new OperationException(ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
372 public void setModelElementOperation(final ModelElementOperation modelElementOperation) {
373 this.modelElementOperation = modelElementOperation;