re base code
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / operations / impl / GroupTypeOperation.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.sdc.be.model.operations.impl;
22
23 import com.google.common.base.Strings;
24 import com.thinkaurelius.titan.graphdb.query.TitanPredicate;
25 import fj.data.Either;
26 import org.apache.commons.collections.CollectionUtils;
27 import org.apache.commons.collections.MapUtils;
28 import org.apache.commons.lang.StringUtils;
29 import org.apache.commons.lang3.tuple.ImmutablePair;
30 import org.openecomp.sdc.be.config.BeEcompErrorManager;
31 import org.openecomp.sdc.be.dao.graph.datatype.GraphEdge;
32 import org.openecomp.sdc.be.dao.graph.datatype.GraphRelation;
33 import org.openecomp.sdc.be.dao.neo4j.GraphEdgeLabels;
34 import org.openecomp.sdc.be.dao.neo4j.GraphEdgePropertiesDictionary;
35 import org.openecomp.sdc.be.dao.neo4j.GraphPropertiesDictionary;
36 import org.openecomp.sdc.be.dao.titan.TitanGenericDao;
37 import org.openecomp.sdc.be.dao.titan.TitanOperationStatus;
38 import org.openecomp.sdc.be.datatypes.elements.GroupTypeDataDefinition;
39 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
40 import org.openecomp.sdc.be.model.CapabilityDefinition;
41 import org.openecomp.sdc.be.model.CapabilityTypeDefinition;
42 import org.openecomp.sdc.be.model.GroupTypeDefinition;
43 import org.openecomp.sdc.be.model.PropertyDefinition;
44 import org.openecomp.sdc.be.model.operations.api.DerivedFromOperation;
45 import org.openecomp.sdc.be.model.operations.api.IGroupTypeOperation;
46 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
47 import org.openecomp.sdc.be.model.operations.api.TypeOperations;
48 import org.openecomp.sdc.be.model.utils.TypeCompareUtils;
49 import org.openecomp.sdc.be.resources.data.*;
50 import org.openecomp.sdc.common.log.wrappers.Logger;
51 import org.springframework.stereotype.Component;
52
53 import java.util.*;
54 import java.util.function.Function;
55 import java.util.stream.Collectors;
56
57 import static org.openecomp.sdc.be.dao.titan.TitanUtils.buildNotInPredicate;
58
59 @Component("group-type-operation")
60 public class GroupTypeOperation implements IGroupTypeOperation {
61
62     private static final Logger log = Logger.getLogger(GroupTypeOperation.class.getName());
63     private static final String CREATE_FLOW_CONTEXT = "CreateGroupType";
64
65     private final PropertyOperation propertyOperation;
66     private final TitanGenericDao titanGenericDao;
67     private final CapabilityTypeOperation capabilityTypeOperation;
68     private final CapabilityOperation capabilityOperation;
69     private final DerivedFromOperation derivedFromOperation;
70     private final OperationUtils operationUtils;
71
72
73     public GroupTypeOperation(TitanGenericDao titanGenericDao,
74                               PropertyOperation propertyOperation,
75                               CapabilityTypeOperation capabilityTypeOperation,
76                               CapabilityOperation capabilityOperation,
77                               DerivedFromOperation derivedFromOperation, OperationUtils operationUtils) {
78         this.titanGenericDao = titanGenericDao;
79         this.propertyOperation = propertyOperation;
80         this.capabilityTypeOperation = capabilityTypeOperation;
81         this.capabilityOperation = capabilityOperation;
82         this.derivedFromOperation = derivedFromOperation;
83         this.operationUtils = operationUtils;
84     }
85
86     @Override
87     public Either<GroupTypeDefinition, StorageOperationStatus> addGroupType(GroupTypeDefinition groupTypeDefinition) {
88         Either<GroupTypeDefinition, StorageOperationStatus> validationRes = validateUpdateProperties(groupTypeDefinition);
89         if (validationRes.isRight()) {
90             log.error("#addGroupType - One or all properties of group type {} not valid. status is {}", groupTypeDefinition, validationRes.right().value());
91             return validationRes;
92         }
93         
94         return addGroupType(groupTypeDefinition, true);
95     }
96
97     @Override
98     public Either<GroupTypeDefinition, StorageOperationStatus> addGroupType(GroupTypeDefinition groupTypeDefinition, boolean inTransaction) {
99
100         Either<GroupTypeDefinition, StorageOperationStatus> result = null;
101
102         try {
103
104             Either<GroupTypeData, TitanOperationStatus> eitherStatus = addGroupTypeToGraph(groupTypeDefinition);
105
106             if (eitherStatus.isRight()) {
107                 BeEcompErrorManager.getInstance().logBeFailedCreateNodeError(CREATE_FLOW_CONTEXT, groupTypeDefinition.getType(), eitherStatus.right().value().name());
108                 result = Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(eitherStatus.right().value()));
109             }
110             else {
111                 result = getGroupType(eitherStatus.left().value().getUniqueId(), inTransaction);
112             }
113
114             return result;
115
116         } finally {
117             titanGenericDao.handleTransactionCommitRollback(inTransaction, result);
118         }
119
120     }
121
122     @Override
123     public Either<GroupTypeDefinition, StorageOperationStatus> updateGroupType(GroupTypeDefinition updatedGroupType, GroupTypeDefinition currGroupType) {
124         log.debug("updating group type {}", updatedGroupType.getType());
125         return updateGroupTypeOnGraph(updatedGroupType, currGroupType);
126     }
127     
128     
129     public Either<GroupTypeDefinition, StorageOperationStatus> validateUpdateProperties(GroupTypeDefinition groupTypeDefinition) {
130         TitanOperationStatus error = null;
131         if (CollectionUtils.isNotEmpty(groupTypeDefinition.getProperties()) && !Strings.isNullOrEmpty(groupTypeDefinition.getDerivedFrom())) {
132             Either<Map<String, PropertyDefinition>, TitanOperationStatus> allPropertiesRes = 
133                                         getAllGroupTypePropertiesFromAllDerivedFrom(groupTypeDefinition.getDerivedFrom());
134             if (allPropertiesRes.isRight() && !allPropertiesRes.right().value().equals(TitanOperationStatus.NOT_FOUND)) {
135                 error = allPropertiesRes.right().value();
136                 log.debug("Couldn't fetch derived from property nodes for group type {}, error: {}", groupTypeDefinition.getType(), error);
137             }
138             if (error == null && !allPropertiesRes.left().value().isEmpty()) {
139                 Either<List<PropertyDefinition>, TitanOperationStatus> validatePropertiesRes = propertyOperation.validatePropertiesUniqueness(allPropertiesRes.left().value(),
140                         groupTypeDefinition.getProperties());
141                 if (validatePropertiesRes.isRight()) {
142                     error = validatePropertiesRes.right().value();
143                 }
144             }
145         }
146         if (error == null) {
147             return Either.left(groupTypeDefinition);
148         }
149         return Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(error));
150     }
151     
152     private Either<Map<String, PropertyDefinition>, TitanOperationStatus> getAllGroupTypePropertiesFromAllDerivedFrom(String firstParentType) {
153         return titanGenericDao.getNode(GraphPropertiesDictionary.TYPE.getProperty(), firstParentType, GroupTypeData.class)
154                     .left()
155                     .bind(parentGroup -> propertyOperation.getAllTypePropertiesFromAllDerivedFrom(parentGroup.getUniqueId(), NodeTypeEnum.GroupType, GroupTypeData.class));
156     }
157
158
159     private StorageOperationStatus mergeCapabilities(GroupTypeDefinition groupTypeDef) {
160         Map<String, CapabilityDefinition> updatedGroupTypeCapabilities = groupTypeDef.getCapabilities();
161         Map<String, CapabilityDefinition> newGroupTypeCapabilities;
162         Either<List<CapabilityDefinition>, StorageOperationStatus> oldCapabilitiesRes = getCapablities(groupTypeDef.getUniqueId());
163         if (oldCapabilitiesRes.isRight()) {
164             StorageOperationStatus status = oldCapabilitiesRes.right().value();
165             if (status == StorageOperationStatus.NOT_FOUND) {
166                 newGroupTypeCapabilities = updatedGroupTypeCapabilities;
167             }
168             else {
169                 return status;
170             }
171         }
172         else {
173             Map<String, CapabilityDefinition> oldCapabilities = asCapabilitiesMap(oldCapabilitiesRes.left().value());
174             newGroupTypeCapabilities = collectNewCapabilities(updatedGroupTypeCapabilities, oldCapabilities);
175
176             for(Map.Entry<String, CapabilityDefinition> oldEntry: oldCapabilities.entrySet()) {
177                 String key = oldEntry.getKey();
178                 CapabilityDefinition newCapDef = updatedGroupTypeCapabilities != null? updatedGroupTypeCapabilities.get(key): null;
179                 CapabilityDefinition oldCapDef = oldEntry.getValue();
180
181                 StorageOperationStatus deleteCapResult = deleteOutdatedCapability(newGroupTypeCapabilities, newCapDef, oldCapDef);
182                 if(deleteCapResult != StorageOperationStatus.OK) {
183                     return deleteCapResult;
184                 }
185             }
186         }
187
188         TitanOperationStatus createCapResult = createCapabilities(new GroupTypeData(groupTypeDef), newGroupTypeCapabilities);
189         return DaoStatusConverter.convertTitanStatusToStorageStatus(createCapResult);
190     }
191
192     /**
193      * @param newGroupTypeCapabilities
194      * @param newCapDef
195      * @param oldCapDef
196      * @return 
197      */
198     private StorageOperationStatus deleteOutdatedCapability(Map<String, CapabilityDefinition> newGroupTypeCapabilities, CapabilityDefinition newCapDef, CapabilityDefinition oldCapDef) {
199         if(!isUpdateAllowed(newCapDef, oldCapDef)) {
200             return StorageOperationStatus.MATCH_NOT_FOUND;
201         }
202
203         if (!TypeCompareUtils.capabilityEquals(oldCapDef, newCapDef)) {
204             StorageOperationStatus deleteCapResult = capabilityOperation.deleteCapability(oldCapDef);
205
206             if(deleteCapResult == StorageOperationStatus.OK) {
207                 newGroupTypeCapabilities.put(newCapDef.getName(), newCapDef);
208             }
209             else {
210                 return deleteCapResult;
211             }
212         }
213         
214         return StorageOperationStatus.OK;
215     }
216
217     private boolean isUpdateAllowed(CapabilityDefinition newCapDef, CapabilityDefinition oldCapDef) {
218         if (newCapDef == null) {
219             log.error("#upsertCapabilities - Failed due to attempt to delete the capability with id {}", oldCapDef.getUniqueId());
220             return false;
221         }
222
223         if (newCapDef.getType() == null || !newCapDef.getType().equals(oldCapDef.getType())) {
224             log.error("#upsertCapabilities - Failed due to attempt to change type of the capability with id {}", oldCapDef.getUniqueId());
225             return false;
226         }
227         
228         return true;
229     }
230
231     /**
232      * @param updatedGroupTypeCapabilities
233      * @param oldCapabilities
234      * @return
235      */
236     private Map<String, CapabilityDefinition> collectNewCapabilities(Map<String, CapabilityDefinition> updatedGroupTypeCapabilities, Map<String, CapabilityDefinition> oldCapabilities) {
237         return updatedGroupTypeCapabilities != null? updatedGroupTypeCapabilities.entrySet().stream()
238                 .filter(entry -> !oldCapabilities.containsKey(entry.getKey()))
239                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) ): null;
240     }
241
242     private TitanOperationStatus createCapabilities(GroupTypeData groupTypeData, Map<String, CapabilityDefinition> groupCapabilities) {
243         if (MapUtils.isEmpty(groupCapabilities)) {
244             return TitanOperationStatus.OK;
245         }
246         
247         return groupCapabilities.values().stream()
248                 .map(v -> createCapability(groupTypeData, v))
249                 .filter(Either::isRight)
250                 .findFirst()
251                 .map(either -> either.right().value())
252                 .orElse(TitanOperationStatus.OK);
253     }
254
255     private Either<GraphRelation, TitanOperationStatus> createCapability(GroupTypeData groupTypeData, CapabilityDefinition  capabilityDef) {
256         Either<CapabilityTypeDefinition, TitanOperationStatus> eitherCapData = capabilityTypeOperation.getCapabilityTypeByType(capabilityDef.getType());
257         return eitherCapData
258                 .left()
259                 .map(CapabilityTypeData::new)
260                 .left()
261                 .bind(capTypeData -> capabilityOperation.addCapabilityToGraph(groupTypeData.getUniqueId(), capTypeData, capabilityDef))
262                 .left()
263                 .bind(capData -> connectToCapability(groupTypeData, capData, capabilityDef.getName()));
264     }
265
266
267     /**
268      * Get capability with all relevant properties
269      * @param groupTypeId
270      * @return
271      */
272     private Either<List<CapabilityDefinition>, StorageOperationStatus> getCapablities(String groupTypeId) {
273         Either<List<ImmutablePair<CapabilityData, GraphEdge>>, TitanOperationStatus> groupCapabilitiesOnGraph =
274                 titanGenericDao.getChildrenNodes(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.GroupType), groupTypeId, GraphEdgeLabels.GROUP_TYPE_CAPABILITY, NodeTypeEnum.Capability, CapabilityData.class, true);
275
276         if (groupCapabilitiesOnGraph.isRight()) {
277             TitanOperationStatus capabilityStatus = groupCapabilitiesOnGraph.right().value();
278             if (capabilityStatus == TitanOperationStatus.NOT_FOUND) {
279                 return Either.left(Collections.emptyList());
280             }
281             return Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(capabilityStatus));
282         }
283
284         List<ImmutablePair<CapabilityData, GraphEdge>> groupCapabilites = groupCapabilitiesOnGraph.left().value();
285         groupCapabilites.forEach(this::fillCapabilityName);
286
287         return capabilityOperation.getCapabilitiesWithProps(groupCapabilites)
288                 .right()
289                 .map(DaoStatusConverter::convertTitanStatusToStorageStatus);
290     }
291
292     private void fillCapabilityName(ImmutablePair<CapabilityData, GraphEdge> pair) {
293         pair.getLeft().getCapabilityDataDefinition().setName((String)pair.getRight().getProperties().get(GraphEdgePropertiesDictionary.NAME.getProperty()));
294     }
295
296     private Either<GraphRelation, TitanOperationStatus> connectToCapability(GroupTypeData groupTypeData, CapabilityData capabilityData, String capabilityName) {
297         Map<String, Object> properties = new HashMap<>();
298         properties.put(GraphEdgePropertiesDictionary.NAME.getProperty(), capabilityName);
299
300         return titanGenericDao.createRelation(groupTypeData, capabilityData, GraphEdgeLabels.GROUP_TYPE_CAPABILITY, properties);
301     }
302
303
304     public List<GroupTypeDefinition> getAllGroupTypes(Set<String> excludedGroupTypes) {
305         Map<String, Map.Entry<TitanPredicate, Object>> predicateCriteria = buildNotInPredicate(GraphPropertiesDictionary.TYPE.getProperty(), excludedGroupTypes);
306         List<GroupTypeData> groupTypes = titanGenericDao.getByCriteriaWithPredicate(NodeTypeEnum.GroupType, predicateCriteria, GroupTypeData.class)
307                 .left()
308                 .on(operationUtils::onTitanOperationFailure);
309         return convertGroupTypesToDefinition(groupTypes);
310     }
311
312
313     private List<GroupTypeDefinition> convertGroupTypesToDefinition(List<GroupTypeData> groupTypes) {
314         return groupTypes.stream()
315                 .map(type -> new GroupTypeDefinition(type.getGroupTypeDataDefinition()))
316                 .collect(Collectors.toList());
317     }
318
319
320     public Either<GroupTypeDefinition, StorageOperationStatus> getGroupTypeByUid(String uniqueId) {
321         log.debug("#getGroupTypeByUid - fetching group type with id {}", uniqueId);
322         return titanGenericDao.getNode(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.GroupType), uniqueId, GroupTypeData.class)
323                 .right()
324                 .map(DaoStatusConverter::convertTitanStatusToStorageStatus)
325                 .left()
326                 .bind(groupType -> buildGroupTypeDefinition(uniqueId, groupType));
327     }
328
329     @Override
330     public Either<GroupTypeDefinition, StorageOperationStatus> getGroupType(String uniqueId, boolean inTransaction) {
331         Either<GroupTypeDefinition, StorageOperationStatus> result = null;
332         try {
333
334             Either<GroupTypeDefinition, StorageOperationStatus> ctResult = getGroupTypeByUid(uniqueId);
335
336             if (ctResult.isRight()) {
337                 StorageOperationStatus status = ctResult.right().value();
338                 if (status != StorageOperationStatus.NOT_FOUND) {
339                     log.error("Failed to retrieve information on element uniqueId: {}. status is {}", uniqueId, status);
340                 }
341                 result = Either.right(ctResult.right().value());
342                 return result;
343             }
344
345             result = Either.left(ctResult.left().value());
346
347             return result;
348         } finally {
349             titanGenericDao.handleTransactionCommitRollback(inTransaction, result);
350         }
351
352     }
353
354     @Override
355     public Either<GroupTypeDefinition, StorageOperationStatus> getLatestGroupTypeByType(String type) {
356         return getLatestGroupTypeByType(type, true);
357     }
358
359     @Override
360     public Either<GroupTypeDefinition, StorageOperationStatus> getLatestGroupTypeByType(String type, boolean inTransaction) {
361         Map<String, Object> mapCriteria = new HashMap<>();
362         mapCriteria.put(GraphPropertiesDictionary.TYPE.getProperty(), type);
363         mapCriteria.put(GraphPropertiesDictionary.IS_HIGHEST_VERSION.getProperty(), true);
364
365         return getGroupTypeByCriteria(type, mapCriteria, inTransaction);
366
367     }
368
369     public Either<GroupTypeDefinition, StorageOperationStatus> getGroupTypeByCriteria(String type, Map<String, Object> properties, boolean inTransaction) {
370         Either<GroupTypeDefinition, StorageOperationStatus> result = null;
371         try {
372             if (type == null || type.isEmpty()) {
373                 log.error("type is empty");
374                 result = Either.right(StorageOperationStatus.INVALID_ID);
375                 return result;
376             }
377
378             Either<List<GroupTypeData>, StorageOperationStatus> groupTypeEither = titanGenericDao.getByCriteria(NodeTypeEnum.GroupType, properties, GroupTypeData.class)
379                     .right()
380                     .map(DaoStatusConverter::convertTitanStatusToStorageStatus);
381             if (groupTypeEither.isRight()) {
382                 result = Either.right(groupTypeEither.right().value());
383             } else {
384                 GroupTypeDataDefinition dataDefinition = groupTypeEither.left().value().stream()
385                         .map(GroupTypeData::getGroupTypeDataDefinition)
386                         .findFirst()
387                         .get();
388                 result = getGroupTypeByUid(dataDefinition.getUniqueId());
389
390             }
391             return result;
392
393         } finally {
394             titanGenericDao.handleTransactionCommitRollback(inTransaction, result);
395         }
396     }
397
398     private Either<GroupTypeDefinition, StorageOperationStatus> buildGroupTypeDefinition(String uniqueId, GroupTypeData groupTypeNode) {
399         GroupTypeDefinition groupType = new GroupTypeDefinition(groupTypeNode.getGroupTypeDataDefinition());
400         return fillDerivedFrom(uniqueId, groupType)
401                 .left()
402                 .map(derivedFrom -> fillProperties(uniqueId, groupType, derivedFrom))
403                 .left()
404                 .bind(props -> fillCapabilities(uniqueId, groupType));
405     }
406     
407     private Either<GroupTypeDefinition, StorageOperationStatus> fillCapabilities(String uniqueId, GroupTypeDefinition groupType) {
408         return getCapablities(uniqueId)
409                 .left()
410                 .map(capabilities -> {
411                     groupType.setCapabilities(asCapabilitiesMap(capabilities));
412                     return groupType;
413                 });
414     }
415
416     private Either<GroupTypeData, StorageOperationStatus> fillDerivedFrom(String uniqueId, GroupTypeDefinition groupType) {
417         log.debug("#fillDerivedFrom - fetching group type {} derived node", groupType.getType());
418         return derivedFromOperation.getDerivedFromChild(uniqueId, NodeTypeEnum.GroupType, GroupTypeData.class)
419                 .right()
420                 .bind(this::handleDerivedFromNotExist)
421                 .left()
422                 .map(derivedFrom -> setDerivedFrom(groupType, derivedFrom));
423
424     }
425
426     private Either<List<PropertyDefinition>, StorageOperationStatus> fillProperties(String uniqueId, GroupTypeDefinition groupType, GroupTypeData derivedFromNode) {
427         log.debug("#fillProperties - fetching all properties for group type {}", groupType.getType());
428         return propertyOperation.findPropertiesOfNode(NodeTypeEnum.GroupType, uniqueId)
429                 .right()
430                 .bind(this::handleGroupTypeHasNoProperties)
431                 .left()
432                 .bind(propsMap -> fillDerivedFromProperties(groupType, derivedFromNode, new ArrayList<>(propsMap.values())));
433     }
434
435     Either<Map<String, PropertyDefinition>, StorageOperationStatus> handleGroupTypeHasNoProperties(TitanOperationStatus err) {
436         if (err == TitanOperationStatus.NOT_FOUND) {
437             return Either.left(new HashMap<>());
438         }
439         return Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(err));
440     }
441
442     private Either<List<PropertyDefinition>, StorageOperationStatus> fillDerivedFromProperties(GroupTypeDefinition groupType, GroupTypeData derivedFromNode, List<PropertyDefinition> groupTypeDirectProperties) {
443         if (derivedFromNode == null) {
444             groupType.setProperties(groupTypeDirectProperties);
445             return Either.left(groupTypeDirectProperties);
446         }
447         log.debug("#fillDerivedFromProperties - fetching all properties of derived from chain for group type {}", groupType.getType());
448         return propertyOperation.getAllPropertiesRec(derivedFromNode.getUniqueId(), NodeTypeEnum.GroupType, GroupTypeData.class)
449                 .left()
450                 .map(derivedFromProps -> {groupTypeDirectProperties.addAll(derivedFromProps); return groupTypeDirectProperties;})
451                 .left()
452                 .map(allProps -> {groupType.setProperties(allProps);return allProps;});
453     }
454
455     private Either<GroupTypeData, StorageOperationStatus> handleDerivedFromNotExist(StorageOperationStatus err) {
456         if (err == StorageOperationStatus.NOT_FOUND) {
457             return Either.left(null);
458         }
459         return Either.right(err);
460     }
461
462     private GroupTypeData setDerivedFrom(GroupTypeDefinition groupTypeDefinition, GroupTypeData derivedFrom) {
463         if (derivedFrom != null) {
464             groupTypeDefinition.setDerivedFrom(derivedFrom.getGroupTypeDataDefinition().getType());
465         }
466         return derivedFrom;
467     }
468
469
470     @Override
471     public Either<GroupTypeDefinition, StorageOperationStatus> getGroupTypeByTypeAndVersion(String type, String version) {
472         return getGroupTypeByTypeAndVersion(type, version, false);
473     }
474
475     @Override
476     public Either<GroupTypeDefinition, StorageOperationStatus> getGroupTypeByTypeAndVersion(String type, String version, boolean inTransaction) {
477         Map<String, Object> mapCriteria = new HashMap<>();
478         mapCriteria.put(GraphPropertiesDictionary.TYPE.getProperty(), type);
479         mapCriteria.put(GraphPropertiesDictionary.VERSION.getProperty(), version);
480
481         return getGroupTypeByCriteria(type, mapCriteria, inTransaction);
482     }
483
484     /**
485      * Add group type to graph.
486      * <p>
487      * 1. Add group type node
488      * <p>
489      * 2. Add edge between the former node to its parent(if exists)
490      * <p>
491      * 3. Add property node and associate it to the node created at #1. (per property & if exists)
492      *
493      * @param groupTypeDefinition
494      * @return
495      */
496     private Either<GroupTypeData, TitanOperationStatus> addGroupTypeToGraph(GroupTypeDefinition groupTypeDefinition) {
497
498         log.debug("Got group type {}", groupTypeDefinition);
499
500         String ctUniqueId = UniqueIdBuilder.buildGroupTypeUid(groupTypeDefinition.getType(), groupTypeDefinition.getVersion(), "grouptype");
501
502         GroupTypeData groupTypeData = buildGroupTypeData(groupTypeDefinition, ctUniqueId);
503
504         log.debug("Before adding group type to graph. groupTypeData = {}", groupTypeData);
505
506         Either<GroupTypeData, TitanOperationStatus> createGTResult = titanGenericDao.createNode(groupTypeData, GroupTypeData.class);
507         log.debug("After adding group type to graph. status is = {}", createGTResult);
508
509         if (createGTResult.isRight()) {
510             TitanOperationStatus operationStatus = createGTResult.right().value();
511             log.error("Failed to add group type {} to graph. status is {}", groupTypeDefinition.getType(), operationStatus);
512             return Either.right(operationStatus);
513         }
514
515         GroupTypeData resultCTD = createGTResult.left().value();
516         List<PropertyDefinition> properties = groupTypeDefinition.getProperties();
517         Either<Map<String, PropertyData>, TitanOperationStatus> addPropertiesToCapablityType = propertyOperation.addPropertiesToElementType(resultCTD.getUniqueId(), NodeTypeEnum.GroupType, properties);
518         if (addPropertiesToCapablityType.isRight()) {
519             log.error("Failed add properties {} to capability {}", properties, groupTypeDefinition.getType());
520             return Either.right(addPropertiesToCapablityType.right().value());
521         }
522
523         String derivedFrom = groupTypeDefinition.getDerivedFrom();
524         if (derivedFrom != null) {
525             Either<GraphRelation, TitanOperationStatus> createRelation = connectToDerivedFrom(ctUniqueId, derivedFrom);
526             if (createRelation.isRight()) {
527                 return Either.right(createRelation.right().value());
528             }
529         }
530         
531         Map<String, CapabilityDefinition> groupCapTypes = groupTypeDefinition.getCapabilities();
532         if (!MapUtils.isEmpty(groupCapTypes)) {
533             TitanOperationStatus status = createCapabilities(groupTypeData, groupCapTypes);
534             if (status != TitanOperationStatus.OK) {
535                 return Either.right(status);
536             }
537         }
538
539         return Either.left(createGTResult.left().value());
540
541     }
542
543
544     private Either<GraphRelation, TitanOperationStatus> connectToDerivedFrom(String ctUniqueId, String derivedFrom) {
545         log.debug("Before creating relation between Group Type with id {} to its parent {}", ctUniqueId, derivedFrom);
546
547         Either<GroupTypeData, TitanOperationStatus> derivedFromGroupTypeResult =
548                 titanGenericDao.getNode(GraphPropertiesDictionary.TYPE.getProperty(), derivedFrom, GroupTypeData.class);
549
550         if (derivedFromGroupTypeResult.isLeft()) {
551             UniqueIdData from = new UniqueIdData(NodeTypeEnum.GroupType, ctUniqueId);
552             GroupTypeData to = derivedFromGroupTypeResult.left().value();
553
554             Either<GraphRelation, TitanOperationStatus> createRelation = titanGenericDao.createRelation(from, to, GraphEdgeLabels.DERIVED_FROM, null);
555             log.debug("After create relation between Group Type with id {} to its parent {}, status is {}.", ctUniqueId, derivedFrom, createRelation);
556             return createRelation;
557         } else {
558             TitanOperationStatus status = derivedFromGroupTypeResult.right().value();
559             log.debug("Failed to found parent Group Type {}, stauts is {}.", derivedFrom, status);
560             return Either.right(status);
561         }
562     }
563
564     private GroupTypeData buildGroupTypeData(GroupTypeDefinition groupTypeDefinition, String ctUniqueId) {
565
566         GroupTypeData groupTypeData = new GroupTypeData(groupTypeDefinition);
567
568         groupTypeData.getGroupTypeDataDefinition().setUniqueId(ctUniqueId);
569         Long creationDate = groupTypeData.getGroupTypeDataDefinition().getCreationTime();
570         if (creationDate == null) {
571             creationDate = System.currentTimeMillis();
572         }
573         groupTypeData.getGroupTypeDataDefinition().setCreationTime(creationDate);
574         groupTypeData.getGroupTypeDataDefinition().setModificationTime(creationDate);
575
576         return groupTypeData;
577     }
578
579     public Either<Boolean, StorageOperationStatus> isCapabilityTypeDerivedFrom(String childCandidateType, String parentCandidateType) {
580         Map<String, Object> propertiesToMatch = new HashMap<>();
581         propertiesToMatch.put(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.CapabilityType), childCandidateType);
582         Either<List<CapabilityTypeData>, TitanOperationStatus> getResponse = titanGenericDao.getByCriteria(NodeTypeEnum.CapabilityType, propertiesToMatch, CapabilityTypeData.class);
583         if (getResponse.isRight()) {
584             TitanOperationStatus titanOperationStatus = getResponse.right().value();
585             log.debug("Couldn't fetch capability type {}, error: {}", childCandidateType, titanOperationStatus);
586             return Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(titanOperationStatus));
587         }
588         String childUniqueId = getResponse.left().value().get(0).getUniqueId();
589         Set<String> travelledTypes = new HashSet<>();
590         do {
591             travelledTypes.add(childUniqueId);
592             Either<List<ImmutablePair<CapabilityTypeData, GraphEdge>>, TitanOperationStatus> childrenNodes = titanGenericDao.getChildrenNodes(UniqueIdBuilder.getKeyByNodeType(NodeTypeEnum.CapabilityType), childUniqueId, GraphEdgeLabels.DERIVED_FROM,
593                     NodeTypeEnum.CapabilityType, CapabilityTypeData.class);
594             if (childrenNodes.isRight()) {
595                 if (childrenNodes.right().value() != TitanOperationStatus.NOT_FOUND) {
596                     TitanOperationStatus titanOperationStatus = getResponse.right().value();
597                     log.debug("Couldn't fetch derived from node for capability type {}, error: {}", childCandidateType, titanOperationStatus);
598                     return Either.right(DaoStatusConverter.convertTitanStatusToStorageStatus(titanOperationStatus));
599                 } else {
600                     log.debug("Derived from node is not found for type {} - this is OK for root capability.", childCandidateType);
601                     return Either.left(false);
602                 }
603             }
604             String derivedFromUniqueId = childrenNodes.left().value().get(0).getLeft().getUniqueId();
605             if (derivedFromUniqueId.equals(parentCandidateType)) {
606                 log.debug("Verified that capability type {} derives from capability type {}", childCandidateType, parentCandidateType);
607                 return Either.left(true);
608             }
609             childUniqueId = derivedFromUniqueId;
610         } while (!travelledTypes.contains(childUniqueId));
611         // this stop condition should never be used, if we use it, we have an
612         // illegal cycle in graph - "derived from" hierarchy cannot be cycled.
613         // It's here just to avoid infinite loop in case we have such cycle.
614         log.error("Detected a cycle of \"derived from\" edges starting at capability type node {}", childUniqueId);
615         return Either.right(StorageOperationStatus.GENERAL_ERROR);
616     }
617     
618     /**
619      * @param list
620      * @return
621      */
622     private Map<String, CapabilityDefinition> asCapabilitiesMap(List<CapabilityDefinition> list) {
623         return list.stream()
624                 .collect(Collectors.toMap(CapabilityDefinition::getName, Function.identity()));
625     }
626
627
628     private Either<GroupTypeDefinition, StorageOperationStatus> updateGroupTypeOnGraph(GroupTypeDefinition updatedGroupType, GroupTypeDefinition currGroupType) {
629         updateGroupTypeData(updatedGroupType, currGroupType);
630         return titanGenericDao.updateNode(new GroupTypeData(updatedGroupType), GroupTypeData.class)
631                 .right()
632                 .map(DaoStatusConverter::convertTitanStatusToStorageStatus)
633                 .left()
634                 .bind(updatedNode -> updateGroupProperties(updatedGroupType.getUniqueId(), updatedGroupType.getProperties()))
635                 .left()
636                 .bind(updatedProperties -> updateGroupDerivedFrom(updatedGroupType, currGroupType.getDerivedFrom()))
637                 .right()
638                 .bind(result -> TypeOperations.mapOkStatus(result, null))
639                 .left()
640                 .bind(updatedDerivedFrom -> TypeOperations.mapOkStatus(mergeCapabilities(updatedGroupType), updatedGroupType))
641                 .left()
642                 .bind(def -> getGroupTypeByUid(def.getUniqueId()));
643     }
644     
645
646     private Either<Map<String, PropertyData>, StorageOperationStatus> updateGroupProperties(String groupId, List<PropertyDefinition> properties) {
647         log.debug("#updateGroupProperties - updating group type properties for group type with id {}", groupId);
648         Map<String, PropertyDefinition> mapProperties = properties != null? properties.stream()
649                 .collect(Collectors.toMap(PropertyDefinition::getName, Function.identity())): null;
650         return propertyOperation.mergePropertiesAssociatedToNode(NodeTypeEnum.GroupType, groupId, mapProperties)
651                 .right()
652                 .map(DaoStatusConverter::convertTitanStatusToStorageStatus);
653     }
654
655
656
657     private Either<GraphRelation, StorageOperationStatus> updateGroupDerivedFrom(GroupTypeDefinition updatedGroupType, String currDerivedFromGroupType) {
658         
659         String groupTypeId = updatedGroupType.getUniqueId();
660         if (StringUtils.equals(updatedGroupType.getDerivedFrom(), currDerivedFromGroupType)) {
661             return Strings.isNullOrEmpty(currDerivedFromGroupType)? 
662                     Either.right(StorageOperationStatus.OK):
663                         getLatestGroupTypeByType(currDerivedFromGroupType, true)
664                         .left()
665                         .map(def -> null);
666         }
667         
668         StorageOperationStatus status = isLegalToReplaceParent(currDerivedFromGroupType, updatedGroupType.getDerivedFrom(), updatedGroupType.getType());
669         if ( status != StorageOperationStatus.OK) {
670             return Either.right(status);
671         }
672
673         log.debug("#updateGroupDerivedFrom - updating group derived from relation for group type with id {}. old derived type {}. new derived type {}", groupTypeId, currDerivedFromGroupType, updatedGroupType.getDerivedFrom());
674         StorageOperationStatus deleteDerivedRelationStatus = deleteDerivedFromGroupType(groupTypeId, currDerivedFromGroupType);
675         if (deleteDerivedRelationStatus != StorageOperationStatus.OK) {
676             return Either.right(deleteDerivedRelationStatus);
677         }
678         return addDerivedFromRelation(updatedGroupType, groupTypeId);
679     }
680
681     private StorageOperationStatus isLegalToReplaceParent(String oldTypeParent, String newTypeParent, String childType) {
682         return derivedFromOperation.isUpdateParentAllowed(oldTypeParent, newTypeParent, childType, NodeTypeEnum.GroupType, GroupTypeData.class, t -> t.getGroupTypeDataDefinition().getType());
683     }
684     
685     private Either<GraphRelation, StorageOperationStatus> addDerivedFromRelation(GroupTypeDataDefinition groupTypeDef, String gtUniqueId) {
686         String derivedFrom = groupTypeDef.getDerivedFrom();
687         if (derivedFrom == null) {
688             return Either.left(null);
689         }
690         log.debug("#addDerivedFromRelationBefore - adding derived from relation between group type {} to its parent {}", groupTypeDef.getType(), derivedFrom);
691         return this.getLatestGroupTypeByType(derivedFrom, true)
692                 .left()
693                 .bind(derivedFromGroup -> derivedFromOperation.addDerivedFromRelation(gtUniqueId, derivedFromGroup.getUniqueId(), NodeTypeEnum.GroupType));
694     }
695
696     private StorageOperationStatus deleteDerivedFromGroupType(String groupTypeId, String derivedFromType) {
697         if (derivedFromType == null) {
698             return StorageOperationStatus.OK;
699         }
700         log.debug("#deleteDerivedFromGroupType - deleting derivedFrom relation for group type with id {} and its derived type {}", groupTypeId, derivedFromType);
701         return getLatestGroupTypeByType(derivedFromType, true)
702                 .either(derivedFromNode -> derivedFromOperation.removeDerivedFromRelation(groupTypeId, derivedFromNode.getUniqueId(), NodeTypeEnum.GroupType),
703                         err -> err);
704     }
705
706     private void updateGroupTypeData(GroupTypeDefinition updatedTypeDefinition, GroupTypeDefinition currTypeDefinition) {
707         updatedTypeDefinition.setUniqueId(currTypeDefinition.getUniqueId());
708         updatedTypeDefinition.setCreationTime(currTypeDefinition.getCreationTime());
709         updatedTypeDefinition.setModificationTime(System.currentTimeMillis());
710     }
711
712 }