Support querying of model by type
[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 fj.data.Either;
22 import java.nio.charset.StandardCharsets;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.EnumMap;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.Set;
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.JanusGraphGenericDao;
42 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
43 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
44 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
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.operations.api.DerivedFromOperation;
55 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
56 import org.openecomp.sdc.be.resources.data.ModelData;
57 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
58 import org.openecomp.sdc.common.log.wrappers.Logger;
59 import org.springframework.beans.factory.annotation.Autowired;
60 import org.springframework.stereotype.Component;
61 import org.yaml.snakeyaml.Yaml;
62
63 @Component("model-operation")
64 public class ModelOperation {
65
66     private static final Logger log = Logger.getLogger(ModelOperation.class);
67     private static final String ADDITIONAL_TYPE_DEFINITIONS = "additional_type_definitions.yml";
68
69     private final JanusGraphGenericDao janusGraphGenericDao;
70     private final JanusGraphDao janusGraphDao;
71     private final ToscaModelImportCassandraDao toscaModelImportCassandraDao;
72     private final DerivedFromOperation derivedFromOperation;
73
74     @Autowired
75     public ModelOperation(final JanusGraphGenericDao janusGraphGenericDao,
76                           final JanusGraphDao janusGraphDao,
77                           final ToscaModelImportCassandraDao toscaModelImportCassandraDao,
78                           final DerivedFromOperation derivedFromOperation) {
79         this.janusGraphGenericDao = janusGraphGenericDao;
80         this.janusGraphDao = janusGraphDao;
81         this.toscaModelImportCassandraDao = toscaModelImportCassandraDao;
82         this.derivedFromOperation = derivedFromOperation;
83     }
84
85     public Model createModel(final Model model, final boolean inTransaction) {
86         Model result = null;
87         final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
88         try {
89             final Either<ModelData, JanusGraphOperationStatus> createNode = janusGraphGenericDao.createNode(modelData, ModelData.class);
90             if (createNode.isRight()) {
91                 final var janusGraphOperationStatus = createNode.right().value();
92                 log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(), "Problem while creating model, reason {}",
93                     janusGraphOperationStatus);
94                 if (janusGraphOperationStatus == JanusGraphOperationStatus.JANUSGRAPH_SCHEMA_VIOLATION) {
95                     throw ModelOperationExceptionSupplier.modelAlreadyExists(model.getName()).get();
96                 }
97                 throw new OperationException(ActionStatus.GENERAL_ERROR,
98                     String.format("Failed to create model %s on JanusGraph with %s error", model, janusGraphOperationStatus));
99             }
100             addDerivedFromRelation(model);
101             result = new Model(createNode.left().value().getName(), model.getDerivedFrom(), model.getModelType());
102             return result;
103         } finally {
104             if (!inTransaction) {
105                 if (Objects.nonNull(result)) {
106                     janusGraphGenericDao.commit();
107                 } else {
108                     janusGraphGenericDao.rollback();
109                 }
110             }
111         }
112     }
113
114     private void addDerivedFromRelation(final Model model) {
115         final String derivedFrom = model.getDerivedFrom();
116         if (derivedFrom == null) {
117             return;
118         }
119         log.debug("Adding derived from relation between model {} to its parent {}",
120             model.getName(), derivedFrom);
121         final Optional<Model> derivedFromModelOptional = this.findModelByName(derivedFrom);
122         if (derivedFromModelOptional.isPresent()) {
123             final Either<GraphRelation, StorageOperationStatus> result = derivedFromOperation.addDerivedFromRelation(
124                 UniqueIdBuilder.buildModelUid(model.getName()),
125                 UniqueIdBuilder.buildModelUid(derivedFromModelOptional.get().getName()), NodeTypeEnum.Model);
126             if (result.isRight()) {
127                 throw new OperationException(ActionStatus.GENERAL_ERROR,
128                     String.format("Failed to create relationship from model %s to derived from model %s on JanusGraph with %s error", model,
129                         derivedFrom, result.right().value()));
130             }
131         }
132     }
133
134     public Optional<GraphVertex> findModelVertexByName(final String name) {
135         if (StringUtils.isEmpty(name)) {
136             return Optional.empty();
137         }
138         final Map<GraphPropertyEnum, Object> props = new EnumMap<>(GraphPropertyEnum.class);
139         props.put(GraphPropertyEnum.NAME, name);
140         props.put(GraphPropertyEnum.UNIQUE_ID, UniqueIdBuilder.buildModelUid(name));
141         final List<GraphVertex> modelVerticesList = findModelVerticesByCriteria(props);
142         if (modelVerticesList.isEmpty()) {
143             return Optional.empty();
144         }
145         return Optional.ofNullable(modelVerticesList.get(0));
146     }
147
148     public Optional<Model> findModelByName(final String name) {
149         if (StringUtils.isEmpty(name)) {
150             return Optional.empty();
151         }
152         final Optional<GraphVertex> modelVertexOpt = findModelVertexByName(name);
153         if (modelVertexOpt.isEmpty()) {
154             return Optional.empty();
155         }
156
157         final GraphVertex graphVertex = modelVertexOpt.get();
158         return Optional.of(convertToModel(graphVertex));
159     }
160
161     public void createModelImports(final String modelId, final Map<String, byte[]> zipContent) {
162         if (MapUtils.isEmpty(zipContent)) {
163             return;
164         }
165         final List<ToscaImportByModel> toscaImportByModelList = zipContent.entrySet().stream()
166             .map(entry -> {
167                 final String path = entry.getKey();
168                 final byte[] bytes = entry.getValue();
169                 final String content = new String(bytes, StandardCharsets.UTF_8);
170                 final var toscaImportByModel = new ToscaImportByModel();
171                 toscaImportByModel.setModelId(modelId);
172                 toscaImportByModel.setFullPath(path);
173                 toscaImportByModel.setContent(content);
174                 return toscaImportByModel;
175             }).collect(Collectors.toList());
176         toscaModelImportCassandraDao.importAll(modelId, toscaImportByModelList);
177     }
178
179     /**
180      * Finds all the models.
181      *
182      * @return the list of models
183      */
184     public List<Model> findAllModels() {
185         return findModelsByCriteria(Collections.emptyMap());
186     }
187     
188     public List<Model> findModels(final ModelTypeEnum modelType) {
189         final Map<GraphPropertyEnum, Object> propertyCriteria = new EnumMap<>(GraphPropertyEnum.class);
190         propertyCriteria.put(GraphPropertyEnum.MODEL_TYPE, modelType.getValue());
191         
192         return findModelsByCriteria(propertyCriteria);
193     }
194
195     private List<Model> findModelsByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
196         final List<GraphVertex> modelVerticesByCriteria = findModelVerticesByCriteria(propertyCriteria);
197         if (modelVerticesByCriteria.isEmpty()) {
198             return Collections.emptyList();
199         }
200
201         return modelVerticesByCriteria.stream().map(this::convertToModel).collect(Collectors.toList());
202     }
203
204     private List<GraphVertex> findModelVerticesByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
205         final Either<List<GraphVertex>, JanusGraphOperationStatus> result = janusGraphDao.getByCriteria(VertexTypeEnum.MODEL, propertyCriteria);
206         if (result.isRight()) {
207             final var janusGraphOperationStatus = result.right().value();
208             if (janusGraphOperationStatus == JanusGraphOperationStatus.NOT_FOUND) {
209                 return Collections.emptyList();
210             }
211             final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
212             log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
213             throw operationException;
214         }
215         return result.left().value();
216     }
217
218     private Model convertToModel(final GraphVertex modelGraphVertex) {
219         final String modelName = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME);
220         final String modelTypeProperty = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.MODEL_TYPE);
221         final ModelTypeEnum modelType = StringUtils.isEmpty(modelTypeProperty) ? ModelTypeEnum.NORMATIVE : ModelTypeEnum.findByValue(modelTypeProperty).get();
222         
223         final Either<ImmutablePair<ModelData, GraphEdge>, JanusGraphOperationStatus> parentNode =
224             janusGraphGenericDao.getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Model), UniqueIdBuilder.buildModelUid(modelName),
225                 GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, ModelData.class);
226         log.debug("After retrieving DERIVED_FROM node of {}. status is {}", modelName, parentNode);
227         if (parentNode.isRight()) {
228             final JanusGraphOperationStatus janusGraphOperationStatus = parentNode.right().value();
229             if (janusGraphOperationStatus != JanusGraphOperationStatus.NOT_FOUND) {
230                 final var operationException = ModelOperationExceptionSupplier.failedToRetrieveModels(janusGraphOperationStatus).get();
231                 log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
232                 throw operationException;
233             }
234             return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), modelType);
235         } else {
236             final ModelData parentModel = parentNode.left().value().getKey();
237             return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName(), modelType);
238         }
239     }
240
241     public void addTypesToDefaultImports(final String typesYaml, final String modelName) {
242         final List<ToscaImportByModel> allSchemaImportsByModel = toscaModelImportCassandraDao.findAllByModel(modelName);
243         final Optional<ToscaImportByModel> additionalTypeDefinitionsOptional = allSchemaImportsByModel.stream()
244             .filter(t -> ADDITIONAL_TYPE_DEFINITIONS.equals(t.getFullPath())).findAny();
245         final ToscaImportByModel toscaImportByModelAdditionalTypeDefinitions;
246         final List<ToscaImportByModel> schemaImportsByModel;
247         if (additionalTypeDefinitionsOptional.isPresent()) {
248             toscaImportByModelAdditionalTypeDefinitions = additionalTypeDefinitionsOptional.get();
249             schemaImportsByModel = allSchemaImportsByModel.stream()
250                 .filter(toscaImportByModel -> !ADDITIONAL_TYPE_DEFINITIONS.equals(toscaImportByModel.getFullPath()))
251                 .collect(Collectors.toList());
252         } else {
253             toscaImportByModelAdditionalTypeDefinitions = new ToscaImportByModel();
254             toscaImportByModelAdditionalTypeDefinitions.setModelId(modelName);
255             toscaImportByModelAdditionalTypeDefinitions.setFullPath(ADDITIONAL_TYPE_DEFINITIONS);
256             toscaImportByModelAdditionalTypeDefinitions.setContent(typesYaml);
257             schemaImportsByModel = new ArrayList<>(allSchemaImportsByModel);
258         }
259
260         final List<ToscaImportByModel> toscaImportByModels = removeExistingDefaultImports(typesYaml, schemaImportsByModel);
261
262         final Map<String, Object> originalContent = (Map<String, Object>) new Yaml().load(toscaImportByModelAdditionalTypeDefinitions.getContent());
263         toscaImportByModelAdditionalTypeDefinitions.setContent(buildAdditionalTypeDefinitionsContent(typesYaml, originalContent).toString());
264         toscaImportByModels.add(toscaImportByModelAdditionalTypeDefinitions);
265
266         toscaModelImportCassandraDao.importOnly(modelName, toscaImportByModels);
267     }
268
269     private List<ToscaImportByModel> removeExistingDefaultImports(final String typesYaml, final List<ToscaImportByModel> schemaImportsByModel) {
270         final List<ToscaImportByModel> toscaImportByModels = new ArrayList<>();
271         schemaImportsByModel.forEach(toscaImportByModel -> {
272             final ToscaImportByModel toscaImportByModelNew = new ToscaImportByModel();
273             toscaImportByModelNew.setModelId(toscaImportByModel.getModelId());
274             toscaImportByModelNew.setFullPath(toscaImportByModel.getFullPath());
275
276             final Map<String, Object> existingImportYamlMap = (Map<String, Object>) new Yaml().load(toscaImportByModel.getContent());
277
278             ((Map<String, Object>) new Yaml().load(typesYaml)).keySet().forEach(existingImportYamlMap::remove);
279
280             final StringBuilder stringBuilder = new StringBuilder();
281             existingImportYamlMap.forEach((key, value) -> {
282                 final Map<Object, Object> hashMap = new HashMap<>();
283                 hashMap.put(key, value);
284                 stringBuilder.append("\n").append(new YamlUtil().objectToYaml(hashMap));
285             });
286
287             toscaImportByModelNew.setContent(stringBuilder.toString());
288             toscaImportByModels.add(toscaImportByModelNew);
289         });
290         return toscaImportByModels;
291     }
292
293     private StringBuilder buildAdditionalTypeDefinitionsContent(final String typesYaml, final Map<String, Object> originalContent) {
294         final var stringBuilder = new StringBuilder();
295
296         final Map<String, Object> typesYamlMap = (Map<String, Object>) new Yaml().load(typesYaml);
297         final Set<String> typeYmlKeySet = typesYamlMap.keySet();
298
299         originalContent.forEach((key, value) -> {
300             final Map<Object, Object> hashMap = new HashMap<>();
301             if (typeYmlKeySet.contains(key)) {
302                 hashMap.put(key, typesYamlMap.get(key));
303             } else {
304                 hashMap.put(key, value);
305             }
306             final String newContent = new YamlUtil().objectToYaml(hashMap);
307             stringBuilder.append("\n").append(newContent);
308         });
309         return stringBuilder;
310     }
311
312 }