1 package org.onap.sdc.dcae.rule.editor.impl;
3 import com.google.gson.Gson;
4 import com.google.gson.GsonBuilder;
5 import org.apache.commons.lang3.StringUtils;
6 import org.onap.sdc.common.onaplog.OnapLoggerDebug;
7 import org.onap.sdc.common.onaplog.Enums.LogLevel;
8 import org.onap.sdc.dcae.composition.restmodels.ruleeditor.*;
9 import org.onap.sdc.dcae.errormng.ActionStatus;
10 import org.onap.sdc.dcae.errormng.ErrConfMgr;
11 import org.onap.sdc.dcae.errormng.ResponseFormat;
12 import org.onap.sdc.dcae.errormng.ServiceException;
13 import org.onap.sdc.dcae.rule.editor.translators.MappingRulesTranslator;
14 import org.onap.sdc.dcae.rule.editor.utils.EmptyStringTranslationSerializer;
15 import org.onap.sdc.dcae.rule.editor.validators.RuleValidator;
16 import org.springframework.stereotype.Component;
17 import org.springframework.util.CollectionUtils;
20 import java.util.function.BiFunction;
21 import java.util.function.Function;
22 import java.util.stream.Collectors;
25 public class RulesBusinessLogic {
27 protected OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
28 private RuleValidator ruleValidator = RuleValidator.getInstance();
29 private MappingRulesTranslator mappingRulesTranslator = MappingRulesTranslator.getInstance();
30 private static Gson gsonTranslator = new GsonBuilder().registerTypeAdapter(String.class, new EmptyStringTranslationSerializer()).enableComplexMapKeySerialization().create();
32 public List<ServiceException> validateRule(Rule rule) {
33 List<ResponseFormat> errors = new ArrayList<>();
34 if(ruleValidator.validate(rule, errors))
35 detectAndResolveActionDependencies(rule, errors);
36 return errors.stream().map(r -> r.getRequestError().getServiceException()).collect(Collectors.toList());
39 public List<ServiceException> validateRules(MappingRules rules) {
40 List<ResponseFormat> errors = new ArrayList<>();
41 detectAndResolveRuleDependencies(rules, errors);
42 return errors.stream().map(r -> r.getRequestError().getServiceException()).collect(Collectors.toList());
45 public String translateRules(MappingRules rules, String entryPointPhase, String lastPhase, String runPhase) {
46 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "Start translating mapping rules");
47 return gsonTranslator.toJson(mappingRulesTranslator.translateToHpJson(rules, entryPointPhase, lastPhase, runPhase));
50 public boolean addOrEditRule(MappingRules rules, Rule rule) {
51 // in case the rule id is passed but the rule doesn't exist on the mapping rule file:
52 if(StringUtils.isNotBlank(rule.getUid()) && !rules.ruleExists(rule))
54 rules.addOrReplaceRule(rule);
58 public Rule deleteRule(MappingRules rules, String ruleUid) {
59 return rules.removeRule(ruleUid);
62 private <T> List<T> detectDependentItemsByDependencyDefinition(Collection<T> allItems, BiFunction<T, Collection<T>, Boolean> dependencyDefinition) {
63 return allItems.stream().filter(i -> dependencyDefinition.apply(i, allItems)).collect(Collectors.toList());
66 // if all dependencies are resolvable returns empty list
67 // else returns list of non resolvable items (circular dependent items)
68 // iterate through all dependentItems removing resolvable items each iteration.
70 private <T> List<T> detectCircularDependenciesByDependencyDefinition(List<T> dependentItems, BiFunction<T, Collection<T>, Boolean> dependencyDetector) {
71 while(!CollectionUtils.isEmpty(dependentItems)) {
72 List<T> resolvable = dependentItems.stream()
73 .filter(i -> !dependencyDetector.apply(i, dependentItems))
74 .collect(Collectors.toList());
75 if(CollectionUtils.isEmpty(resolvable))
77 dependentItems.removeAll(resolvable);
79 return dependentItems;
82 private <T> List<T> reorderItemsByDependencyDefinition(Collection<T> allItems, BiFunction<T, T, Boolean> dependencyDetector) {
83 List<T> ordered = new ArrayList<>(allItems);
84 allItems.forEach(i -> {
85 List<T> dependencies = allItems.stream().filter(o -> dependencyDetector.apply(i, o)).collect(Collectors.toList());
86 dependencies.forEach(d -> {
87 if(ordered.indexOf(d) > ordered.indexOf(i)) {
89 ordered.add(ordered.indexOf(i), d);
96 private void detectAndResolveActionDependencies(Rule rule, List<ResponseFormat> errors) {
97 List<BaseAction> dependentActions = detectDependentItemsByDependencyDefinition(rule.getActions(), BaseAction::hasDependencies);
98 if(!CollectionUtils.isEmpty(dependentActions)) {
99 List<BaseAction> nonResolvable = detectCircularDependenciesByDependencyDefinition(dependentActions, BaseAction::hasDependencies);
100 if (!CollectionUtils.isEmpty(nonResolvable)) {
101 errors.add(ErrConfMgr.INSTANCE.getResponseFormat(ActionStatus.ACTION_DEPENDENCY, null, nonResolvable.stream().map(BaseAction::getTarget).collect(Collectors.joining(", "))));
104 List<BaseAction> actions = reorderItemsByDependencyDefinition(rule.getActions(), BaseAction::referencesTarget);
105 rule.setActions(actions);
109 // first identify dependent rules
110 // if no dependencies found return true
111 // if non resolvable dependencies found return false
112 // else reorder and return true
114 private void detectAndResolveRuleDependencies(MappingRules rules, List<ResponseFormat> errors) {
115 List<Rule> dependentRules = detectDependentItemsByDependencyDefinition(rules.getRules().values(), Rule::referencesOtherRules);
116 if(!CollectionUtils.isEmpty(dependentRules)) {
117 List<Rule> nonResolvable = detectCircularDependenciesByDependencyDefinition(dependentRules, Rule::referencesOtherRules);
118 if (!CollectionUtils.isEmpty(nonResolvable)) {
119 String nonResolvableRuleIds = nonResolvable.stream().map(Rule::getUid).collect(Collectors.joining(", "));
120 errors.add(ErrConfMgr.INSTANCE.getResponseFormat(ActionStatus.RULE_DEPENDENCY, null, nonResolvableRuleIds, extractDependentActionTargetsFromRules(nonResolvable)));
123 reorderRulesByDependency(rules);
127 private String extractDependentActionTargetsFromRules(List<Rule> dependentRules) {
128 List<BaseAction> allActions = dependentRules.stream().map(Rule::getActions).flatMap(List::stream).collect(Collectors.toList());
129 // option 1: circular dependency between actions
130 List<BaseAction> nonResolvable = detectCircularDependenciesByDependencyDefinition(allActions, BaseAction::hasDependencies);
131 if(CollectionUtils.isEmpty(nonResolvable))
132 // option 2: circular dependency between rules - collect dependent actions and condition dependencies
133 nonResolvable = dependentRules.stream()
134 .map(r -> r.findDependencies(dependentRules))
135 .flatMap(List::stream)
136 .collect(Collectors.toList());
137 return nonResolvable.stream()
138 .map(BaseAction::getTarget)
139 .collect(Collectors.joining(", "));
142 private void reorderRulesByDependency(MappingRules rules) {
143 List<Rule> ordered = reorderItemsByDependencyDefinition(rules.getRules().values(), Rule::referencesOtherRule);
144 Map<String, Rule> rulesMap = ordered.stream().collect(Collectors.toMap(Rule::getUid, Function.identity(), (u, v) -> {
145 throw new IllegalStateException(String.format("Duplicate key %s", u));
146 }, LinkedHashMap::new));
147 rules.setRules(rulesMap);