Support for adding artifact types
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / operations / impl / ArtifactTypeOperation.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
20 package org.openecomp.sdc.be.model.operations.impl;
21
22 import fj.data.Either;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.stream.Collectors;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.commons.lang3.tuple.ImmutablePair;
32 import org.openecomp.sdc.be.dao.api.ActionStatus;
33 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
34 import org.openecomp.sdc.be.dao.graph.datatype.GraphNode;
35 import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation;
36 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphGenericDao;
37 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
38 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
39 import org.openecomp.sdc.be.dao.neo4j.GraphPropertiesDictionary;
40 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
41 import org.openecomp.sdc.be.model.ArtifactTypeDefinition;
42 import org.openecomp.sdc.be.model.PropertyDefinition;
43 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.ModelOperationExceptionSupplier;
44 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.exception.OperationException;
45 import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation;
46 import org.openecomp.sdc.be.model.operations.api.IArtifactTypeOperation;
47 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
48 import org.openecomp.sdc.be.resources.data.ArtifactTypeData;
49 import org.openecomp.sdc.be.resources.data.ModelData;
50 import org.openecomp.sdc.be.resources.data.PropertyData;
51 import org.openecomp.sdc.be.resources.data.UniqueIdData;
52 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
53 import org.openecomp.sdc.common.log.wrappers.Logger;
54 import org.springframework.beans.factory.annotation.Autowired;
55 import org.springframework.stereotype.Component;
56
57 /**
58  * This class is responsible for handling all operations for the TOSCA artifact types
59  */
60 @Component("artifact-type-operation")
61 public class ArtifactTypeOperation implements IArtifactTypeOperation {
62
63     private static final Logger LOGGER = Logger.getLogger(ArtifactTypeOperation.class.getName());
64
65     private final JanusGraphGenericDao janusGraphGenericDao;
66     private final PropertyOperation propertyOperation;
67     private final DerivedFromOperation derivedFromOperation;
68     private final ModelOperation modelOperation;
69
70     @Autowired
71     public ArtifactTypeOperation(final JanusGraphGenericDao janusGraphGenericDao,
72                                  final PropertyOperation propertyOperation,
73                                  final DerivedFromOperation derivedFromOperation,
74                                  final ModelOperation modelOperation) {
75         this.janusGraphGenericDao = janusGraphGenericDao;
76         this.propertyOperation = propertyOperation;
77         this.derivedFromOperation = derivedFromOperation;
78         this.modelOperation = modelOperation;
79     }
80
81     /**
82      * Creates a TOSCA artifact types
83      * @param artifactType the TOSCA artifact types definition to be created
84      * @return the created TOSCA artifact types definition
85      */
86     @Override
87     public ArtifactTypeDefinition createArtifactType(final ArtifactTypeDefinition artifactType) {
88         return createArtifactType(artifactType, false);
89     }
90
91     @Override
92     public ArtifactTypeDefinition createArtifactType(final ArtifactTypeDefinition artifactType,
93                                                      final boolean inTransaction) {
94         Either<ArtifactTypeData, JanusGraphOperationStatus> createNodeResult = null;
95         try {
96             artifactType.setUniqueId(UniqueIdBuilder.buildArtifactTypeUid(artifactType.getModel(), artifactType.getType()));
97             final ArtifactTypeData artifactTypeData = new ArtifactTypeData(artifactType);
98             final Either<ArtifactTypeData, JanusGraphOperationStatus> existArtifact = janusGraphGenericDao
99                 .getNode(artifactTypeData.getUniqueIdKey(), artifactTypeData.getUniqueId(), ArtifactTypeData.class);
100             if (!existArtifact.isLeft()) {
101                 createNodeResult = janusGraphGenericDao.createNode(artifactTypeData, ArtifactTypeData.class);
102                 if (createNodeResult.isRight()) {
103                     final JanusGraphOperationStatus operationStatus = createNodeResult.right().value();
104                     LOGGER.error(EcompLoggerErrorCode.DATA_ERROR,
105                         "Failed to add artifact type {} to graph. Operation status {}", artifactType.getType(), operationStatus);
106                     throw new OperationException(ActionStatus.GENERAL_ERROR,
107                         String.format("Failed to create artifact type %s with status %s", artifactType.getType(),
108                             DaoStatusConverter.convertJanusGraphStatusToStorageStatus(operationStatus)));
109                 }
110                 addPropertiesToArtifactType(artifactType);
111                 addModelRelation(artifactType);
112                 addDerivedFromRelation(artifactType);
113                 return convertArtifactDataDefinition(createNodeResult.left().value());
114             } else {
115                 LOGGER.debug("Artifact type already exist {}", artifactType);
116                 return convertArtifactDataDefinition(existArtifact.left().value());
117             }
118         } finally {
119             if (!inTransaction) {
120                 if (createNodeResult == null || createNodeResult.isRight()) {
121                     LOGGER.debug("Rollback on graph.");
122                     janusGraphGenericDao.rollback();
123                 } else {
124                     janusGraphGenericDao.commit();
125                 }
126             }
127         }
128     }
129
130     /**
131      * Finds all TOSCA artifact types applicable to the given model name
132      * @param model model name
133      * @return all the TOSCA artifact types found
134      */
135     @Override
136     public Map<String, ArtifactTypeDefinition> getAllArtifactTypes(final String model) {
137         final Either<List<ArtifactTypeData>, JanusGraphOperationStatus> artifactTypes =
138             janusGraphGenericDao.getByCriteriaForModel(NodeTypeEnum.ArtifactType, Collections.emptyMap(), model, ArtifactTypeData.class);
139         if (artifactTypes.isRight()) {
140             if (JanusGraphOperationStatus.NOT_FOUND == artifactTypes.right().value()) {
141                 return Collections.emptyMap();
142             }
143             throw new OperationException(ActionStatus.GENERAL_ERROR,
144                 String.format("Failed to find artifact on JanusGraph with status %s",
145                     DaoStatusConverter.convertJanusGraphStatusToStorageStatus(artifactTypes.right().value())));
146         }
147         final Map<String, ArtifactTypeDefinition> artifactTypeDefinitionTypes = new HashMap<>();
148         final List<ArtifactTypeDefinition> artifactTypeList = artifactTypes.left().value().stream()
149             .map(this::convertArtifactDataDefinition)
150             .filter(artifactTypeDefinition -> artifactTypeDefinition.getUniqueId().equalsIgnoreCase(UniqueIdBuilder
151                 .buildArtifactTypeUid(artifactTypeDefinition.getModel(), artifactTypeDefinition.getType())))
152             .collect(Collectors.toList());
153         for (final ArtifactTypeDefinition type : artifactTypeList) {
154             artifactTypeDefinitionTypes.put(type.getUniqueId(), type);
155         }
156         return artifactTypeDefinitionTypes;
157     }
158
159     /**
160      * Coverts the given artifact type data into a TOSCA artifact types definition
161      * @param artifactTypeData artifact type data representation
162      * @return the TOSCA artifact types definition
163      */
164     private ArtifactTypeDefinition convertArtifactDataDefinition(final ArtifactTypeData artifactTypeData) {
165         LOGGER.debug("The object returned after create tosca artifact type is {}", artifactTypeData);
166         final ArtifactTypeDefinition artifactType = new ArtifactTypeDefinition(artifactTypeData.getArtifactTypeDataDefinition());
167         final var modelAssociatedToArtifactOptional = getModelAssociatedToArtifact(artifactTypeData.getUniqueId());
168         if (!modelAssociatedToArtifactOptional.isEmpty()) {
169             artifactType.setModel(modelAssociatedToArtifactOptional.get());
170         }
171         artifactType.setType(artifactTypeData.getArtifactTypeDataDefinition().getType());
172         final ArtifactTypeData derivedFromNode = fillDerivedFrom(artifactType);
173         fillProperties(artifactType, derivedFromNode);
174         return artifactType;
175     }
176
177     /**
178      * Finds an artifact type data on JanusGraph based on the given parameters
179      * @param type the artifact type derived from
180      * @param model the model name
181      * @return the optional artifact type data found
182      */
183     private Optional<ArtifactTypeData> getLatestArtifactTypeByType(final String type, final String model) {
184         final Map<String, Object> mapCriteria = new HashMap<>();
185         mapCriteria.put(GraphPropertiesDictionary.TYPE.getProperty(), type);
186         final Either<List<ArtifactTypeData>, JanusGraphOperationStatus> result = janusGraphGenericDao.getByCriteriaForModel(NodeTypeEnum.ArtifactType,
187             mapCriteria, model, ArtifactTypeData.class);
188         if (result.isRight()) {
189             final JanusGraphOperationStatus status = result.right().value();
190             if (JanusGraphOperationStatus.NOT_FOUND == status) {
191                 return Optional.empty();
192             }
193             throw new OperationException(ActionStatus.GENERAL_ERROR,
194                 String.format("Failed to find artifact by type on JanusGraph with status %s",
195                     DaoStatusConverter.convertJanusGraphStatusToStorageStatus(status)));
196         }
197         return Optional.of(result.left().value().get(0));
198     }
199
200     /**
201      * Creates the derivedFrom relation for the given TOSCA artifact types
202      * @param artifactType the TOSCA artifact types definition
203      */
204     private void addDerivedFromRelation(final ArtifactTypeDefinition artifactType) {
205         final String derivedFrom = artifactType.getDerivedFrom();
206         final String artifactId = artifactType.getUniqueId();
207         if (derivedFrom.isEmpty()) {
208             return;
209         }
210         final var getArtifactTypeOptional = getLatestArtifactTypeByType(derivedFrom, artifactType.getModel());
211         if (getArtifactTypeOptional.isPresent()) {
212             if (derivedFromOperation.addDerivedFromRelation(artifactId, getArtifactTypeOptional.get().getUniqueId(),
213                 NodeTypeEnum.ArtifactType).isRight()) {
214                 throw new OperationException(ActionStatus.GENERAL_ERROR,
215                     String.format("Failed creating derivedFrom relation for artifact type %s", artifactType.getType()));
216             }
217         }
218     }
219
220     /**
221      * Adds a property definition to the given TOSCA artifact types definition
222      * @param artifactType the TOSCA artifact types
223      */
224     private void addPropertiesToArtifactType(final ArtifactTypeDefinition artifactType) {
225         final List<PropertyDefinition> properties = artifactType.getProperties();
226         final Either<Map<String, PropertyData>, JanusGraphOperationStatus> addPropertiesToArtifactType =
227             propertyOperation.addPropertiesToElementType(artifactType.getUniqueId(), NodeTypeEnum.ArtifactType, properties);
228         if (addPropertiesToArtifactType.isRight()) {
229             throw new OperationException(ActionStatus.GENERAL_ERROR,
230                 String.format("Failed creating properties for artifact type %s with status %s",
231                     artifactType.getType(), DaoStatusConverter.convertJanusGraphStatusToStorageStatus(addPropertiesToArtifactType.right().value())));
232         }
233     }
234
235     /**
236      * Creates an edge between the given TOSCA artifact types and it`s model
237      * @param artifactType the TOSCA artifact types
238      */
239     private void addModelRelation(final ArtifactTypeDefinition artifactType) {
240         final String model = artifactType.getModel();
241         if (StringUtils.isNotEmpty(model)) {
242             final GraphNode from = new UniqueIdData(NodeTypeEnum.Model, UniqueIdBuilder.buildModelUid(model));
243             final GraphNode to = new UniqueIdData(NodeTypeEnum.ArtifactType, artifactType.getUniqueId());
244             LOGGER.info("Connecting model {} to type {}", from, to);
245             final Either<GraphRelation, JanusGraphOperationStatus> status = janusGraphGenericDao.createRelation(from,
246                 to, GraphEdgeLabels.MODEL_ELEMENT, Collections.emptyMap());
247             if (status.isRight()) {
248                 throw new OperationException(ActionStatus.GENERAL_ERROR,
249                     String.format("Failed creating relation between model %s and artifact type %s with status %s",
250                         model, artifactType.getType(), DaoStatusConverter.convertJanusGraphStatusToStorageStatus(status.right().value())));
251             }
252         }
253     }
254
255     /**
256      * Finds a model associated to the given artifact type unique id
257      * @param uid the TOSCA artifact types unique id
258      * @return
259      */
260     private Optional<String> getModelAssociatedToArtifact(final String uid) {
261         final Either<ImmutablePair<ModelData, GraphEdge>, JanusGraphOperationStatus> model =
262             janusGraphGenericDao.getParentNode(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.Interface), uid,
263                 GraphEdgeLabels.MODEL_ELEMENT, NodeTypeEnum.Model, ModelData.class);
264         if (model.isLeft()) {
265             return Optional.ofNullable(model.left().value().getLeft().getName());
266         }
267         return Optional.empty();
268     }
269
270     /**
271      * Finds the derived from for teh given TOSCA artifact types
272      * @param artifactType
273      * @return
274      */
275     private ArtifactTypeData fillDerivedFrom(final ArtifactTypeDefinition artifactType) {
276         final Either<ArtifactTypeData, StorageOperationStatus> result = derivedFromOperation
277             .getDerivedFromChild(artifactType.getUniqueId(), NodeTypeEnum.ArtifactType, ArtifactTypeData.class)
278             .right().bind(this::handleDerivedFromNotExist).left()
279             .map(derivedFrom -> setDerivedFrom(artifactType, derivedFrom));
280         if (result.isRight()) {
281             throw new OperationException(ActionStatus.GENERAL_ERROR,
282                 String.format("Failed fetching derivedFrom for artifact type %s with status %s",
283                     artifactType.getType(), result.right().value()));
284         }
285         return result.left().value();
286     }
287
288     private Either<ArtifactTypeData, StorageOperationStatus> handleDerivedFromNotExist(final StorageOperationStatus storageOperationStatus) {
289         if (storageOperationStatus == StorageOperationStatus.NOT_FOUND) {
290             return Either.left(null);
291         }
292         return Either.right(storageOperationStatus);
293     }
294
295     private ArtifactTypeData setDerivedFrom(final ArtifactTypeDefinition artifactType, final ArtifactTypeData derivedFrom) {
296         if (derivedFrom != null) {
297             artifactType.setDerivedFrom(derivedFrom.getArtifactTypeDataDefinition().getType());
298         }
299         return derivedFrom;
300     }
301
302     /**
303      * Finds all properties for the given TOSCA artifact types
304      * @param artifactType the TOSCA artifact types
305      * @param derivedFromNode the TOSCA artifact types derived from
306      */
307     private void fillProperties(final ArtifactTypeDefinition artifactType, final ArtifactTypeData derivedFromNode) {
308         final Either<List<PropertyDefinition>, StorageOperationStatus> result =
309             propertyOperation.findPropertiesOfNode(NodeTypeEnum.ArtifactType, artifactType.getUniqueId()).right()
310                 .bind(this::handleNoProperties).left().bind(propsMap -> fillDerivedFromProperties(artifactType,
311                     derivedFromNode, new ArrayList<>(propsMap.values())));
312         if (result.isRight()) {
313             throw new OperationException(ActionStatus.GENERAL_ERROR,
314                 String.format("Failed fetching properties for artifact type %s with status %s",
315                     artifactType.getType(), result.right().value()));
316         }
317     }
318
319     private Either<Map<String, PropertyDefinition>, StorageOperationStatus> handleNoProperties(
320         final JanusGraphOperationStatus janusGraphOperationStatus) {
321         if (janusGraphOperationStatus == JanusGraphOperationStatus.NOT_FOUND) {
322             return Either.left(new HashMap<>());
323         }
324         return Either.right(DaoStatusConverter.convertJanusGraphStatusToStorageStatus(janusGraphOperationStatus));
325     }
326
327     private Either<List<PropertyDefinition>, StorageOperationStatus> fillDerivedFromProperties(final ArtifactTypeDefinition artifactType,
328                                                                                                final ArtifactTypeData derivedFromNode,
329                                                                                                final List<PropertyDefinition> artifactTypeProperties)
330     {
331         if (derivedFromNode == null) {
332             artifactType.setProperties(artifactTypeProperties);
333             return Either.left(artifactTypeProperties);
334         }
335         return propertyOperation
336             .getAllPropertiesRec(derivedFromNode.getUniqueId(), NodeTypeEnum.ArtifactType, ArtifactTypeData.class)
337             .left().map(derivedFromProps -> {
338                 artifactTypeProperties.addAll(derivedFromProps);
339                 return artifactTypeProperties;
340             }).left().map(allProps -> {
341                 artifactType.setProperties(allProps);
342                 return allProps;
343             });
344     }
345
346     /**
347      * The Model field is an optional entry when uploading a resource. If the field is present, it validates if the Model name exists.
348      * @param modelName Model names declared on the resource json representation
349      */
350     public void validateModel(final String modelName) {
351         if (modelOperation.findModelByName(modelName).isEmpty()) {
352             LOGGER.error(EcompLoggerErrorCode.DATA_ERROR,"Could not find model name {}", modelName);
353             throw ModelOperationExceptionSupplier.invalidModel(modelName).get();
354         }
355     }
356 }