Support updated data types in service import
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / operations / impl / ModelOperation.java
index ce1f574..0d462c9 100644 (file)
  */
 package org.openecomp.sdc.be.model.operations.impl;
 
+import static org.openecomp.sdc.common.api.Constants.ADDITIONAL_TYPE_DEFINITIONS;
+
 import fj.data.Either;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
@@ -38,18 +44,21 @@ import org.openecomp.sdc.be.dao.api.ActionStatus;
 import org.openecomp.sdc.be.dao.cassandra.ToscaModelImportCassandraDao;
 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
 import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation;
+import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao;
 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
 import org.openecomp.sdc.be.dao.jsongraph.GraphVertex;
-import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
 import org.openecomp.sdc.be.dao.jsongraph.types.VertexTypeEnum;
 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
 import org.openecomp.sdc.be.data.model.ToscaImportByModel;
 import org.openecomp.sdc.be.datatypes.enums.GraphPropertyEnum;
+import org.openecomp.sdc.be.datatypes.enums.ModelTypeEnum;
 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
+import org.openecomp.sdc.be.model.DataTypeDefinition;
 import org.openecomp.sdc.be.model.Model;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier;
 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
+import org.openecomp.sdc.be.model.normatives.ElementTypeEnum;
 import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation;
 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
 import org.openecomp.sdc.be.resources.data.ModelData;
@@ -63,12 +72,13 @@ import org.yaml.snakeyaml.Yaml;
 public class ModelOperation {
 
     private static final Logger log = Logger.getLogger(ModelOperation.class);
-    private static final String ADDITIONAL_TYPE_DEFINITIONS = "additional_type_definitions.yml";
+    static final Path ADDITIONAL_TYPE_DEFINITIONS_PATH = Path.of(ADDITIONAL_TYPE_DEFINITIONS);
 
     private final JanusGraphGenericDao janusGraphGenericDao;
     private final JanusGraphDao janusGraphDao;
     private final ToscaModelImportCassandraDao toscaModelImportCassandraDao;
     private final DerivedFromOperation derivedFromOperation;
+    private ModelElementOperation modelElementOperation;
 
     @Autowired
     public ModelOperation(final JanusGraphGenericDao janusGraphGenericDao,
@@ -83,7 +93,7 @@ public class ModelOperation {
 
     public Model createModel(final Model model, final boolean inTransaction) {
         Model result = null;
-        final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()));
+        final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
         try {
             final Either<ModelData, JanusGraphOperationStatus> createNode = janusGraphGenericDao.createNode(modelData, ModelData.class);
             if (createNode.isRight()) {
@@ -97,7 +107,7 @@ public class ModelOperation {
                     String.format("Failed to create model %s on JanusGraph with %s error", model, janusGraphOperationStatus));
             }
             addDerivedFromRelation(model);
-            result = new Model(createNode.left().value().getName(), model.getDerivedFrom());
+            result = new Model(createNode.left().value().getName(), model.getDerivedFrom(), model.getModelType());
             return result;
         } finally {
             if (!inTransaction) {
@@ -172,7 +182,33 @@ public class ModelOperation {
                 toscaImportByModel.setContent(content);
                 return toscaImportByModel;
             }).collect(Collectors.toList());
-        toscaModelImportCassandraDao.importAll(modelId, toscaImportByModelList);
+        toscaModelImportCassandraDao.replaceImports(modelId, toscaImportByModelList);
+    }
+
+    /**
+     * Find all the model default imports, with the option to include the default imports from the parent model.
+     *
+     * @param modelId       the model id
+     * @param includeParent a flag to include the parent model imports.
+     * @return the list of model default imports, or an empty list if no imports were found.
+     */
+    public List<ToscaImportByModel> findAllModelImports(final String modelId, final boolean includeParent) {
+        final List<ToscaImportByModel> toscaImportByModelList = toscaModelImportCassandraDao.findAllByModel(modelId);
+        if (includeParent) {
+            findModelByName(modelId).ifPresent(model -> {
+                if (model.getDerivedFrom() != null) {
+                    toscaImportByModelList.addAll(toscaModelImportCassandraDao.findAllByModel(model.getDerivedFrom()));
+                }
+            });
+        }
+        toscaImportByModelList.sort((o1, o2) -> {
+            final int modelIdComparison = o1.getModelId().compareTo(o2.getModelId());
+            if (modelIdComparison == 0) {
+                return o1.getFullPath().compareTo(o2.getFullPath());
+            }
+            return modelIdComparison;
+        });
+        return toscaImportByModelList;
     }
 
     /**
@@ -183,6 +219,13 @@ public class ModelOperation {
     public List<Model> findAllModels() {
         return findModelsByCriteria(Collections.emptyMap());
     }
+    
+    public List<Model> findModels(final ModelTypeEnum modelType) {
+        final Map<GraphPropertyEnum, Object> propertyCriteria = new EnumMap<>(GraphPropertyEnum.class);
+        propertyCriteria.put(GraphPropertyEnum.MODEL_TYPE, modelType.getValue());
+        
+        return findModelsByCriteria(propertyCriteria);
+    }
 
     private List<Model> findModelsByCriteria(final Map<GraphPropertyEnum, Object> propertyCriteria) {
         final List<GraphVertex> modelVerticesByCriteria = findModelVerticesByCriteria(propertyCriteria);
@@ -209,7 +252,13 @@ public class ModelOperation {
 
     private Model convertToModel(final GraphVertex modelGraphVertex) {
         final String modelName = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME);
-
+        final String modelTypeProperty = (String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.MODEL_TYPE);
+        ModelTypeEnum modelType = ModelTypeEnum.NORMATIVE;
+        final Optional<ModelTypeEnum> optionalModelTypeEnum = ModelTypeEnum.findByValue(modelTypeProperty);
+        if (optionalModelTypeEnum.isPresent()) {
+            modelType = optionalModelTypeEnum.get();
+        }
+        
         final Either<ImmutablePair<ModelData, GraphEdge>, JanusGraphOperationStatus> parentNode =
             janusGraphGenericDao.getChild(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Model), UniqueIdBuilder.buildModelUid(modelName),
                 GraphEdgeLabels.DERIVED_FROM, NodeTypeEnum.Model, ModelData.class);
@@ -221,82 +270,164 @@ public class ModelOperation {
                 log.error(EcompLoggerErrorCode.DATA_ERROR, this.getClass().getName(), operationException.getMessage());
                 throw operationException;
             }
-            return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME));
+            return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), modelType);
         } else {
             final ModelData parentModel = parentNode.left().value().getKey();
-            return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName());
+            return new Model((String) modelGraphVertex.getMetadataProperty(GraphPropertyEnum.NAME), parentModel.getName(), modelType);
         }
     }
 
-    public void addTypesToDefaultImports(final String typesYaml, final String modelName) {
-        final List<ToscaImportByModel> allSchemaImportsByModel = toscaModelImportCassandraDao.findAllByModel(modelName);
-        final Optional<ToscaImportByModel> additionalTypeDefinitionsOptional = allSchemaImportsByModel.stream()
-            .filter(t -> ADDITIONAL_TYPE_DEFINITIONS.equals(t.getFullPath())).findAny();
-        final ToscaImportByModel toscaImportByModelAdditionalTypeDefinitions;
-        final List<ToscaImportByModel> schemaImportsByModel;
-        if (additionalTypeDefinitionsOptional.isPresent()) {
-            toscaImportByModelAdditionalTypeDefinitions = additionalTypeDefinitionsOptional.get();
-            schemaImportsByModel = allSchemaImportsByModel.stream()
-                .filter(toscaImportByModel -> !ADDITIONAL_TYPE_DEFINITIONS.equals(toscaImportByModel.getFullPath()))
+    public void addTypesToDefaultImports(final ElementTypeEnum elementTypeEnum, final String typesYaml, final String modelName) {
+        final List<ToscaImportByModel> modelImportList = toscaModelImportCassandraDao.findAllByModel(modelName);
+        final Optional<ToscaImportByModel> additionalTypeDefinitionsImportOptional = modelImportList.stream()
+            .filter(t -> ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(t.getFullPath()))).findAny();
+        final ToscaImportByModel additionalTypeDefinitionsImport;
+        final List<ToscaImportByModel> rebuiltModelImportList;
+        if (additionalTypeDefinitionsImportOptional.isPresent()) {
+            additionalTypeDefinitionsImport = additionalTypeDefinitionsImportOptional.get();
+            rebuiltModelImportList = modelImportList.stream()
+                .filter(toscaImportByModel -> !ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(toscaImportByModel.getFullPath())))
                 .collect(Collectors.toList());
         } else {
-            toscaImportByModelAdditionalTypeDefinitions = new ToscaImportByModel();
-            toscaImportByModelAdditionalTypeDefinitions.setModelId(modelName);
-            toscaImportByModelAdditionalTypeDefinitions.setFullPath(ADDITIONAL_TYPE_DEFINITIONS);
-            toscaImportByModelAdditionalTypeDefinitions.setContent(typesYaml);
-            schemaImportsByModel = new ArrayList<>(allSchemaImportsByModel);
+            additionalTypeDefinitionsImport = new ToscaImportByModel();
+            additionalTypeDefinitionsImport.setModelId(modelName);
+            additionalTypeDefinitionsImport.setFullPath(ADDITIONAL_TYPE_DEFINITIONS_PATH.toString());
+            additionalTypeDefinitionsImport.setContent(createAdditionalTypeDefinitionsHeader());
+            rebuiltModelImportList = new ArrayList<>(modelImportList);
         }
 
-        final List<ToscaImportByModel> toscaImportByModels = removeExistingDefaultImports(typesYaml, schemaImportsByModel);
+        final Map<String, Object> typesYamlMap = new Yaml().load(typesYaml);
+        removeExistingTypesFromDefaultImports(elementTypeEnum, typesYamlMap, rebuiltModelImportList);
 
-        final Map<String, Object> originalContent = (Map<String, Object>) new Yaml().load(toscaImportByModelAdditionalTypeDefinitions.getContent());
-        toscaImportByModelAdditionalTypeDefinitions.setContent(buildAdditionalTypeDefinitionsContent(typesYaml, originalContent).toString());
-        toscaImportByModels.add(toscaImportByModelAdditionalTypeDefinitions);
+        final Map<String, Object> originalContent = new Yaml().load(additionalTypeDefinitionsImport.getContent());
+        additionalTypeDefinitionsImport.setContent(buildAdditionalTypeDefinitionsContent(elementTypeEnum, typesYamlMap, originalContent));
+        rebuiltModelImportList.add(additionalTypeDefinitionsImport);
 
-        toscaModelImportCassandraDao.importOnly(modelName, toscaImportByModels);
+        toscaModelImportCassandraDao.saveAll(modelName, rebuiltModelImportList);
     }
 
-    private List<ToscaImportByModel> removeExistingDefaultImports(final String typesYaml, final List<ToscaImportByModel> schemaImportsByModel) {
-        final List<ToscaImportByModel> toscaImportByModels = new ArrayList<>();
-        schemaImportsByModel.forEach(toscaImportByModel -> {
-            final ToscaImportByModel toscaImportByModelNew = new ToscaImportByModel();
-            toscaImportByModelNew.setModelId(toscaImportByModel.getModelId());
-            toscaImportByModelNew.setFullPath(toscaImportByModel.getFullPath());
+    private void removeExistingTypesFromDefaultImports(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYaml,
+                                                       final List<ToscaImportByModel> defaultImportList) {
+        defaultImportList.forEach(toscaImportByModel -> {
+            final Map<String, Object> existingImportYamlMap = new Yaml().load(toscaImportByModel.getContent());
+            final Map<String, Object> currentTypeYamlMap = (Map<String, Object>) existingImportYamlMap.get(elementTypeEnum.getToscaEntryName());
+            if (MapUtils.isNotEmpty(currentTypeYamlMap)) {
+                typesYaml.keySet().forEach(currentTypeYamlMap::remove);
+            }
+            toscaImportByModel.setContent(new YamlUtil().objectToYaml(existingImportYamlMap));
+        });
+    }
 
-            final Map<String, Object> existingImportYamlMap = (Map<String, Object>) new Yaml().load(toscaImportByModel.getContent());
+    private String buildAdditionalTypeDefinitionsContent(final ElementTypeEnum elementTypeEnum, final Map<String, Object> typesYamlMap,
+                                                         final Map<String, Object> originalContent) {
+        final Map<String, Object> originalTypeContent = (Map<String, Object>) originalContent.get(elementTypeEnum.getToscaEntryName());
+        if (MapUtils.isEmpty(originalTypeContent)) {
+            originalContent.put(elementTypeEnum.getToscaEntryName(), new LinkedHashMap<>(typesYamlMap));
+        } else {
+            originalTypeContent.putAll(typesYamlMap);
+        }
+        return new YamlUtil().objectToYaml(originalContent);
+    }
 
-            ((Map<String, Object>) new Yaml().load(typesYaml)).keySet().forEach(existingImportYamlMap::remove);
+    private String createAdditionalTypeDefinitionsHeader() {
+        return "tosca_definitions_version: tosca_simple_yaml_1_3" + "\n"
+            + "description: Auto-generated file that contains package custom types or types added after system installation." + "\n";
+    }
 
-            final StringBuilder stringBuilder = new StringBuilder();
-            existingImportYamlMap.forEach((key, value) -> {
-                final Map<Object, Object> hashMap = new HashMap<>();
-                hashMap.put(key, value);
-                stringBuilder.append("\n").append(new YamlUtil().objectToYaml(hashMap));
-            });
+    /**
+     * Deletes the given model if it exists, along with its MODEL_ELEMENT edges and import files.
+     *
+     * @param model         the model
+     * @param inTransaction if the operation is called in the middle of a janusgraph transaction
+     */
+    public void deleteModel(final Model model, final boolean inTransaction) {
+        boolean rollback = false;
 
-            toscaImportByModelNew.setContent(stringBuilder.toString());
-            toscaImportByModels.add(toscaImportByModelNew);
-        });
-        return toscaImportByModels;
+        try {
+            final GraphVertex modelVertexByName = findModelVertexByName(model.getName()).orElse(null);
+            if (modelVertexByName == null) {
+                return;
+            }
+            toscaModelImportCassandraDao.deleteAllByModel(model.getName());
+            modelElementOperation.deleteModelElements(model, inTransaction);
+            deleteModel(model);
+        } catch (final OperationException e) {
+            rollback = true;
+            throw e;
+        } catch (final Exception e) {
+            rollback = true;
+            throw new OperationException(e, ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
+        } finally {
+            if (!inTransaction) {
+                if (rollback) {
+                    janusGraphGenericDao.rollback();
+                } else {
+                    janusGraphGenericDao.commit();
+                }
+            }
+        }
+    }
+
+    private void deleteModel(final Model model) {
+        final var modelData = new ModelData(model.getName(), UniqueIdBuilder.buildModelUid(model.getName()), model.getModelType());
+        final Either<ModelData, JanusGraphOperationStatus> deleteParentNodeByModel = janusGraphGenericDao.deleteNode(modelData, ModelData.class);
+        if (deleteParentNodeByModel.isRight()) {
+            final var janusGraphOperationStatus = deleteParentNodeByModel.right().value();
+            log.error(EcompLoggerErrorCode.DATA_ERROR, ModelOperation.class.getName(),
+                "Failed to delete model {} on JanusGraph with status {}", new Object[] {model.getName(), janusGraphOperationStatus});
+            throw new OperationException(ActionStatus.COULD_NOT_DELETE_MODEL, model.getName());
+        }
+    }
+
+    @Autowired
+    public void setModelElementOperation(final ModelElementOperation modelElementOperation) {
+        this.modelElementOperation = modelElementOperation;
     }
 
-    private StringBuilder buildAdditionalTypeDefinitionsContent(final String typesYaml, final Map<String, Object> originalContent) {
-        final var stringBuilder = new StringBuilder();
+    @SuppressWarnings("unchecked")
+    public void updateTypesInAdditionalTypesImport(final ElementTypeEnum elementTypeEnum, final String typesYaml, final String modelName) {
+        final Optional<ToscaImportByModel> additionalTypeDefinitionsImportOptional = getAdditionalTypes(modelName);
+
+        if (additionalTypeDefinitionsImportOptional.isPresent()) {
+
+            final Map<String, Object> existingTypeContent = getExistingTypes(elementTypeEnum, additionalTypeDefinitionsImportOptional.get());
+            final Set<String> existingTypeNames = existingTypeContent.keySet();
+
+            final Map<String, Object> typesToUpate = new HashMap<>();
+
+            final Map<String, Object> newTypesYaml = new Yaml().load(typesYaml);
+            newTypesYaml.entrySet().stream().filter(entry -> existingTypeNames.contains(entry.getKey())).forEach(newTypeToUpdate -> {
+
+                final Map<String, Object> propertiesInNewDef = (Map<String, Object>) ((Map<String, Object>) newTypeToUpdate.getValue()).get("properties");
+                final Map<String, Object> existingProperties =
+                        (Map<String, Object>) ((Map<String, Object>) existingTypeContent.get(newTypeToUpdate.getKey())).get("properties");
 
-        final Map<String, Object> typesYamlMap = (Map<String, Object>) new Yaml().load(typesYaml);
-        final Set<String> typeYmlKeySet = typesYamlMap.keySet();
+                final List<Entry<String, Object>> propertiesMissingFromNewDef = MapUtils.isEmpty(existingProperties) ? Collections.emptyList()
+                        : existingProperties.entrySet().stream()
+                                .filter(existingPropEntry -> !propertiesInNewDef.keySet().contains(existingPropEntry.getKey()))
+                                .collect(Collectors.toList());
 
-        originalContent.forEach((key, value) -> {
-            final Map<Object, Object> hashMap = new HashMap<>();
-            if (typeYmlKeySet.contains(key)) {
-                hashMap.put(key, typesYamlMap.get(key));
-            } else {
-                hashMap.put(key, value);
+                if (CollectionUtils.isNotEmpty(propertiesMissingFromNewDef)) {
+                    typesToUpate.put(newTypeToUpdate.getKey(), newTypeToUpdate.getValue());
+
+                    propertiesMissingFromNewDef
+                            .forEach(existingPropToAdd -> propertiesInNewDef.put(existingPropToAdd.getKey(), existingPropToAdd.getValue()));
+                }
+            });
+            if (MapUtils.isNotEmpty(typesToUpate)) {
+                addTypesToDefaultImports(elementTypeEnum, new Yaml().dumpAsMap(typesToUpate), modelName);
             }
-            final String newContent = new YamlUtil().objectToYaml(hashMap);
-            stringBuilder.append("\n").append(newContent);
-        });
-        return stringBuilder;
+        }
+    }
+    
+    private  Optional<ToscaImportByModel> getAdditionalTypes(final String modelName) {
+        final List<ToscaImportByModel> modelImportList = toscaModelImportCassandraDao.findAllByModel(modelName);
+        return modelImportList.stream().filter(t -> ADDITIONAL_TYPE_DEFINITIONS_PATH.equals(Path.of(t.getFullPath()))).findAny();
+    }
+    
+    private Map<String, Object> getExistingTypes(final ElementTypeEnum elementTypeEnum, final ToscaImportByModel additionalTypeDefinitionsImport) {
+        final Map<String, Object> existingContent = new Yaml().load(additionalTypeDefinitionsImport.getContent());
+        return  (Map<String, Object>) existingContent.get(elementTypeEnum.getToscaEntryName());
     }
 
 }