diff --git a/.gitignore b/.gitignore index 372e03f0..f0f81b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ dependency-reduced-pom.xml #CI !.ci + +.antlr diff --git a/drools-ansible-rulebook-integration-api/pom.xml b/drools-ansible-rulebook-integration-api/pom.xml index 6c8da9b1..f3f21e95 100644 --- a/drools-ansible-rulebook-integration-api/pom.xml +++ b/drools-ansible-rulebook-integration-api/pom.xml @@ -14,6 +14,11 @@ Drools :: Ansible Rulebook Integration :: API + + org.drools + drools-ansible-rulebook-integration-protoextractor + ${project.version} + org.slf4j slf4j-simple diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/RulesExecutor.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/RulesExecutor.java index fc4f8034..f7590baa 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/RulesExecutor.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/RulesExecutor.java @@ -4,6 +4,7 @@ import org.drools.ansible.rulebook.integration.api.rulesengine.RulesExecutorSession; import org.drools.ansible.rulebook.integration.api.rulesengine.SessionStats; import org.drools.base.facttemplates.Fact; +import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.Match; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,4 +96,8 @@ public String getAllFactsAsJson() { public CompletableFuture> advanceTime(long amount, TimeUnit unit ) { return rulesEvaluator.advanceTime(amount, unit ); } + + public KieSession asKieSession() { + return rulesEvaluator.asKieSession(); + } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/conditions/ConditionExpression.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/conditions/ConditionExpression.java index 6be1258d..bd6a302a 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/conditions/ConditionExpression.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/conditions/ConditionExpression.java @@ -6,6 +6,8 @@ import java.util.Map; import org.drools.ansible.rulebook.integration.api.domain.RuleGenerationContext; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpression; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; import org.drools.model.PrototypeDSL; import org.drools.model.PrototypeExpression; import org.drools.model.PrototypeVariable; @@ -37,8 +39,8 @@ public ConditionExpression(PrototypeExpression prototypeExpression, boolean fiel public ConditionExpression composeWith(PrototypeExpression.BinaryOperation.Operator decodeBinaryOperator, ConditionExpression rhs) { if (isBeta() && rhs.isBeta() && !prototypeName.equals(rhs.getPrototypeName())) { - PrototypeExpression composed = prototypeField(betaVariable, getFieldName()) - .composeWith(decodeBinaryOperator, prototypeField(rhs.getBetaVariable(), rhs.getFieldName())); + PrototypeExpression composed = ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(betaVariable, getFieldName()) + .composeWith(decodeBinaryOperator, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(rhs.getBetaVariable(), rhs.getFieldName())); return new ConditionExpression(composed); } @@ -54,7 +56,7 @@ public boolean isBeta() { } public String getFieldName() { - return ((PrototypeExpression.PrototypeFieldValue) prototypeExpression).getFieldName(); + return prototypeExpression.getIndexingKey().orElseThrow(IllegalStateException::new); } public PrototypeExpression getPrototypeExpression() { @@ -142,7 +144,7 @@ private static ConditionExpression createFieldExpression(RuleGenerationContext r betaVariable = (PrototypeVariable) boundPattern.getFirstVariable(); } } - return new ConditionExpression(fieldName2PrototypeExpression(fieldName), true, prototypeName, betaVariable); + return new ConditionExpression(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(fieldName), true, prototypeName, betaVariable); } private static PrototypeExpression.BinaryOperation.Operator decodeBinaryOperator(String operator) { diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/ExistsField.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/ExistsField.java index db3a48af..4a539d09 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/ExistsField.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/ExistsField.java @@ -6,10 +6,9 @@ import org.drools.ansible.rulebook.integration.api.domain.RuleGenerationContext; import org.drools.ansible.rulebook.integration.api.rulesmodel.ParsedCondition; import org.drools.model.ConstraintOperator; -import org.drools.model.PrototypeFact; +import org.drools.model.Prototype; import static org.drools.ansible.rulebook.integration.api.domain.conditions.ConditionExpression.map2Expr; -import static org.drools.model.PrototypeExpression.fixedValue; import static org.drools.model.PrototypeExpression.thisPrototype; public enum ExistsField implements ConstraintOperator, ConditionFactory { @@ -21,7 +20,7 @@ public enum ExistsField implements ConstraintOperator, ConditionFactory { @Override public BiPredicate asPredicate() { - return (t,v) -> ((PrototypeFact) t).has((String) v); + return (t, v) -> v != Prototype.UNDEFINED_VALUE; // actually, always true from caller: https://github.com/kiegroup/drools/blob/9de8d0b54b364bda1b1d76d81923c8bfc060c2f8/drools-model/drools-canonical-model/src/main/java/org/drools/model/PrototypeDSL.java#L273 } @Override @@ -31,6 +30,6 @@ public String toString() { @Override public ParsedCondition createParsedCondition(RuleGenerationContext ruleContext, String expressionName, Map expression) { - return new ParsedCondition(thisPrototype(), this, fixedValue(map2Expr(ruleContext, expression).getFieldName())).withNotPattern(expressionName.equals(NEGATED_EXPRESSION_NAME)); + return new ParsedCondition(thisPrototype(), this, map2Expr(ruleContext, expression).getPrototypeExpression()).withNotPattern(expressionName.equals(NEGATED_EXPRESSION_NAME)); } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/NotExistsField.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/NotExistsField.java index c678c860..f920a4e7 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/NotExistsField.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/NotExistsField.java @@ -5,13 +5,14 @@ import org.drools.model.ConstraintOperator; import org.drools.model.PrototypeFact; +@Deprecated() // TODO seems no longer in-use? public enum NotExistsField implements ConstraintOperator { INSTANCE; @Override public BiPredicate asPredicate() { - return (t,v) -> !((PrototypeFact) t).has((String) v); + throw new UnsupportedOperationException("deprecated and should be no longer in use"); } @Override diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraint.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraint.java index 9d38c747..46b6512b 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraint.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraint.java @@ -8,6 +8,7 @@ import org.drools.ansible.rulebook.integration.api.domain.conditions.ConditionExpression; import org.drools.ansible.rulebook.integration.api.rulesmodel.BetaParsedCondition; import org.drools.ansible.rulebook.integration.api.rulesmodel.ParsedCondition; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; import org.drools.model.ConstraintOperator; import org.drools.model.Prototype; import org.drools.model.PrototypeDSL; @@ -63,8 +64,8 @@ private static ParsedCondition createSelectConditionWithLeftAndRightFields(RuleG PrototypeDSL.PrototypePatternDef rightPattern = ruleContext.getBoundPattern(rightPatternBindName); return rightPattern == null ? - new ParsedCondition(prototypeField(leftField), operator, prototypeField(rightField)) : - new BetaParsedCondition(prototypeField(leftField), operator, (PrototypeVariable) rightPattern.getFirstVariable(), prototypeField(rightField.substring(dotPos+1))); + new ParsedCondition(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftField), operator, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(rightField)) : + new BetaParsedCondition(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftField), operator, (PrototypeVariable) rightPattern.getFirstVariable(), ExtractorPrototypeExpressionUtils.prototypeFieldExtractorSkippingFirst(rightField)); } static ParsedCondition createSelectConditionWithFixedRight(ConditionExpression left, BiPredicate opPred, Object rhsValue, boolean positive) { diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAbstractTimeConstraint.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAbstractTimeConstraint.java index 097cafab..d372769a 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAbstractTimeConstraint.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAbstractTimeConstraint.java @@ -1,7 +1,15 @@ package org.drools.ansible.rulebook.integration.api.domain.temporal; +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorParser; +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorUtils; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpression; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; +import org.drools.base.facttemplates.Fact; import org.drools.model.Index; import org.drools.model.PrototypeDSL; +import org.drools.model.PrototypeExpression; +import org.drools.model.PrototypeFact; import org.drools.model.PrototypeVariable; import java.util.List; @@ -18,13 +26,13 @@ public abstract class OnceAbstractTimeConstraint implements TimeConstraint { protected String ruleName; protected final TimeAmount timeAmount; - protected final List groupByAttributes; + protected final List groupByAttributes; protected PrototypeDSL.PrototypePatternDef guardedPattern; private PrototypeVariable controlVariable; - public OnceAbstractTimeConstraint(TimeAmount timeAmount, List groupByAttributes) { + public OnceAbstractTimeConstraint(TimeAmount timeAmount, List groupByAttributes) { this.timeAmount = timeAmount; this.groupByAttributes = groupByAttributes; } @@ -39,10 +47,13 @@ protected PrototypeVariable getControlVariable() { protected PrototypeDSL.PrototypePatternDef createControlPattern() { PrototypeDSL.PrototypePatternDef controlPattern = protoPattern(variable(getPrototype(SYNTHETIC_PROTOTYPE_NAME))); - for (String unique : groupByAttributes) { - controlPattern.expr( prototypeField(unique), Index.ConstraintType.EQUAL, getPatternVariable(), prototypeField(unique) ); + for (GroupByAttribute unique : groupByAttributes) { + controlPattern.expr( prototypeField(unique.getKey()), // intentional, the control fact has the "group by" key string as-is (not structured), so we reference it for the left part + Index.ConstraintType.EQUAL, + getPatternVariable(), + unique.asPrototypeExpression() ); // on the right, we need extractor to check the real fact/event attribute value } - controlPattern.expr( prototypeField("drools_rule_name"), Index.ConstraintType.EQUAL, fixedValue(ruleName) ); + controlPattern.expr( ExtractorPrototypeExpressionUtils.prototypeFieldExtractor("drools_rule_name"), Index.ConstraintType.EQUAL, fixedValue(ruleName) ); this.controlVariable = (PrototypeVariable) controlPattern.getFirstVariable(); return controlPattern; } @@ -62,4 +73,39 @@ static String sanitizeAttributeName(String name) { } return name; } + + public static class GroupByAttribute { + private final String key; + private final ExtractorNode extractor; + + private GroupByAttribute(String key, ExtractorNode extractor) { + this.key = key; + this.extractor = extractor; + } + + public static GroupByAttribute from(String expr) { + return new GroupByAttribute(expr, ExtractorParser.parse(expr)); + } + + public String getKey() { + return key; + } + + public PrototypeExpression asPrototypeExpression() { + return new ExtractorPrototypeExpression(extractor); + } + + public Object evalExtractorOnFact(PrototypeFact fact) { + return evalExtractorOnFact((Fact) fact); + } + + public Object evalExtractorOnFact(Fact fact) { + return ExtractorUtils.getValueFrom(extractor, fact.asMap()); + } + + @Override + public String toString() { + return "GroupByAttribute [key=" + key + ", extractor=" + extractor + "]"; + } + } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAfterDefinition.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAfterDefinition.java index a72d7b28..ec2fe7a5 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAfterDefinition.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceAfterDefinition.java @@ -140,7 +140,7 @@ private static Object controlFact2Event(Fact fact) { return writeMetaDataOnEvent((Fact) fact.get("event"), ruleEngineMeta); } - public OnceAfterDefinition(TimeAmount timeAmount, List groupByAttributes) { + public OnceAfterDefinition(TimeAmount timeAmount, List groupByAttributes) { super(timeAmount, groupByAttributes); } @@ -194,8 +194,8 @@ public List getControlRules(RuleGenerationContext ruleContext) { not( createControlPattern() ), on(getPatternVariable()).execute((drools, event) -> { Event controlEvent = createMapBasedEvent( controlPrototype ); - for (String unique : groupByAttributes) { - controlEvent.set(unique, event.get(unique)); + for (GroupByAttribute unique : groupByAttributes) { + controlEvent.set(unique.getKey(), unique.evalExtractorOnFact(event)); } controlEvent.set("drools_rule_name", ruleName); controlEvent.set( "event", event ); @@ -250,7 +250,10 @@ public String toString() { } public static OnceAfterDefinition parseOnceAfter(String onceWithin, List groupByAttributes) { - List sanitizedAttributes = groupByAttributes.stream().map(OnceAbstractTimeConstraint::sanitizeAttributeName).collect(toList()); + List sanitizedAttributes = groupByAttributes.stream() + .map(OnceAbstractTimeConstraint::sanitizeAttributeName) + .map(GroupByAttribute::from) + .collect(toList()); return new OnceAfterDefinition(parseTimeAmount(onceWithin), sanitizedAttributes); } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceWithinDefinition.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceWithinDefinition.java index 349cb101..aff4bb26 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceWithinDefinition.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/domain/temporal/OnceWithinDefinition.java @@ -70,7 +70,7 @@ public class OnceWithinDefinition extends OnceAbstractTimeConstraint { public static final String KEYWORD = "once_within"; - public OnceWithinDefinition(TimeAmount timeAmount, List groupByAttributes) { + public OnceWithinDefinition(TimeAmount timeAmount, List groupByAttributes) { super(timeAmount, groupByAttributes); } @@ -89,8 +89,8 @@ public void executeTimeConstraintConsequence(Drools drools, Object... facts) { Event controlEvent = createMapBasedEvent( getPrototype(SYNTHETIC_PROTOTYPE_NAME) ) .withExpiration(timeAmount.getAmount(), timeAmount.getTimeUnit()); Fact fact = (Fact) facts[0]; - for (String unique : groupByAttributes) { - controlEvent.set(unique, fact.get(unique)); + for (GroupByAttribute unique : groupByAttributes) { + controlEvent.set(unique.getKey(), unique.evalExtractorOnFact(fact)); } controlEvent.set("drools_rule_name", ruleName); drools.insert(controlEvent); @@ -127,7 +127,10 @@ public String toString() { } public static OnceWithinDefinition parseOnceWithin(String onceWithin, List groupByAttributes) { - List sanitizedAttributes = groupByAttributes.stream().map(OnceAbstractTimeConstraint::sanitizeAttributeName).collect(toList()); + List sanitizedAttributes = groupByAttributes.stream() + .map(OnceAbstractTimeConstraint::sanitizeAttributeName) + .map(GroupByAttribute::from) + .collect(toList()); return new OnceWithinDefinition(parseTimeAmount(onceWithin), sanitizedAttributes); } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/AbstractRulesEvaluator.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/AbstractRulesEvaluator.java index ee8b4542..fc24cd4e 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/AbstractRulesEvaluator.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/AbstractRulesEvaluator.java @@ -19,6 +19,7 @@ import org.drools.ansible.rulebook.integration.api.io.RuleExecutorChannel; import org.drools.base.facttemplates.Fact; import org.drools.core.common.InternalFactHandle; +import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.Match; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -247,4 +248,9 @@ private List atomicRuleEvaluation(boolean processEventInsertion, Supplier return matches; } + + @Override + public KieSession asKieSession() { + return rulesExecutorSession.asKieSession(); + } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesEvaluator.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesEvaluator.java index 4d71ae88..a37cbc3a 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesEvaluator.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesEvaluator.java @@ -1,6 +1,7 @@ package org.drools.ansible.rulebook.integration.api.rulesengine; import org.drools.ansible.rulebook.integration.api.RulesExecutorContainer; +import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.Match; import java.util.Collection; @@ -41,4 +42,6 @@ public interface RulesEvaluator { static RulesEvaluator createRulesEvaluator( RulesExecutorSession rulesExecutorSession, boolean async ) { return async ? new AsyncRulesEvaluator(rulesExecutorSession) : new SyncRulesEvaluator(rulesExecutorSession); } + + KieSession asKieSession(); } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesExecutorSession.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesExecutorSession.java index d7f37078..db0c9ebc 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesExecutorSession.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesengine/RulesExecutorSession.java @@ -16,7 +16,6 @@ import java.util.function.BiPredicate; import java.util.stream.Collectors; -import static org.drools.ansible.rulebook.integration.api.rulesmodel.RulesModelUtil.ORIGINAL_MAP_FIELD; import static org.drools.ansible.rulebook.integration.api.rulesmodel.RulesModelUtil.mapToFact; @@ -107,7 +106,7 @@ private static boolean isKeyToBeIgnored(String wmFactKey, String... keysToExclud } } } - return wmFactKey.equals(ORIGINAL_MAP_FIELD); + return false; } int fireAllRules() { @@ -165,4 +164,8 @@ void setExecuteActions(boolean executeActions) { public boolean isMatchMultipleRules() { return rulesSet.isMatchMultipleRules(); } + + public KieSession asKieSession() { + return kieSession; + } } diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/ParsedCondition.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/ParsedCondition.java index be59d3ac..104fbdbe 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/ParsedCondition.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/ParsedCondition.java @@ -2,6 +2,7 @@ import org.drools.ansible.rulebook.integration.api.domain.RuleGenerationContext; import org.drools.ansible.rulebook.integration.api.domain.constraints.NegationOperator; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; import org.drools.model.ConstraintOperator; import org.drools.model.PrototypeDSL; import org.drools.model.PrototypeExpression; @@ -22,7 +23,7 @@ public class ParsedCondition { private boolean negated = false; public ParsedCondition(String left, ConstraintOperator operator, Object right) { - this(prototypeField(left), operator, fixedValue(right)); + this(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(left), operator, fixedValue(right)); } public ParsedCondition(PrototypeExpression left, ConstraintOperator operator, PrototypeExpression right) { diff --git a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/RulesModelUtil.java b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/RulesModelUtil.java index 070388db..58730683 100644 --- a/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/RulesModelUtil.java +++ b/drools-ansible-rulebook-integration-api/src/main/java/org/drools/ansible/rulebook/integration/api/rulesmodel/RulesModelUtil.java @@ -12,51 +12,29 @@ import static org.drools.modelcompiler.facttemplate.FactFactory.createMapBasedFact; public class RulesModelUtil { - - public static final String ORIGINAL_MAP_FIELD = "$_ORIGINAL_$_MAP_$_FIELD_$"; public static final String META_FIELD = "meta"; public static final String RULE_ENGINE_META_FIELD = "rule_engine"; private RulesModelUtil() { } public static Fact mapToFact(Map factMap, boolean event) { - Fact fact = event ? createMapBasedEvent( getPrototype(DEFAULT_PROTOTYPE_NAME) ) : createMapBasedFact( getPrototype(DEFAULT_PROTOTYPE_NAME) ); - populateFact(fact, factMap, ""); - fact.set(ORIGINAL_MAP_FIELD, factMap); + Fact fact = event ? createMapBasedEvent( getPrototype(DEFAULT_PROTOTYPE_NAME), factMap ) : createMapBasedFact( getPrototype(DEFAULT_PROTOTYPE_NAME), factMap ); return fact; } - private static void populateFact(Fact fact, Map value, String fieldName) { - for (Map.Entry entry : value.entrySet()) { - String key = fieldName + entry.getKey(); - fact.set(key, entry.getValue()); - if (entry.getValue() instanceof Map) { - populateFact(fact, (Map) entry.getValue(), key + "."); - } - } - } - public static Object factToMap(Object fact) { if (fact instanceof Fact) { - return factToMap(((Fact) fact).asMap()); - } - if (fact instanceof Map) { - return factToMap(((Map) fact)); + return ((Fact) fact).asMap(); } return fact; } - private static Map factToMap(Map factMap) { - Map map = (Map) factMap.get(ORIGINAL_MAP_FIELD); - return map != null ? map : factMap; - } - public static Map asFactMap(String json) { return new JSONObject(json).toMap(); } public static Fact writeMetaDataOnEvent(Fact event, Map ruleEngineMeta) { - Map map = (Map) event.get(ORIGINAL_MAP_FIELD); + Map map = (Map) event.asMap(); Map meta = (Map) map.computeIfAbsent(META_FIELD, x -> new HashMap<>()); meta.put(RULE_ENGINE_META_FIELD, ruleEngineMeta); return event; diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AdditionTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AdditionTest.java index 198b04bc..af3600f1 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AdditionTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AdditionTest.java @@ -3,6 +3,7 @@ import java.util.List; import org.drools.ansible.rulebook.integration.api.domain.RulesSet; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; import org.drools.model.Index; import org.junit.Test; import org.kie.api.runtime.rule.Match; @@ -70,7 +71,7 @@ public void testExecuteRules() { public void testExecuteRulesSet() { RulesSet rulesSet = new RulesSet(); rulesSet.addRule().withCondition().all() - .addSingleCondition(prototypeField("nested.i"), Index.ConstraintType.EQUAL, prototypeField("nested.j").add(fixedValue(1))); + .addSingleCondition(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor("nested.i"), Index.ConstraintType.EQUAL, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor("nested.j").add(fixedValue(1))); RulesExecutor rulesExecutor = RulesExecutorFactory.createRulesExecutor(rulesSet); diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AlphaTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AlphaTest.java index d439ee01..37133fbb 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AlphaTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/AlphaTest.java @@ -1,11 +1,27 @@ package org.drools.ansible.rulebook.integration.api; -import java.util.List; - +import org.drools.ansible.rulebook.integration.api.rulesmodel.PrototypeFactory; +import org.drools.base.definitions.InternalKnowledgePackage; +import org.drools.base.facttemplates.FactTemplate; +import org.drools.base.facttemplates.FactTemplateObjectType; +import org.drools.base.rule.IndexableConstraint; +import org.drools.base.util.index.ConstraintTypeOperator; +import org.drools.core.reteoo.AlphaNode; +import org.drools.core.reteoo.EntryPointNode; +import org.drools.core.reteoo.ObjectSink; +import org.drools.core.reteoo.ObjectTypeNode; +import org.drools.core.reteoo.Rete; +import org.drools.kiesession.rulebase.InternalKnowledgeBase; +import org.drools.model.Prototype; +import org.drools.modelcompiler.facttemplate.FactFactory; import org.junit.Test; +import org.kie.api.definition.rule.Rule; import org.kie.api.runtime.rule.Match; +import java.util.List; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class AlphaTest { @@ -31,7 +47,7 @@ public void testEqualsWithFixedValue() { " ]\n" + " },\n" + " \"enabled\": true,\n" + - " \"name\": null\n" + + " \"name\": \"R1\"\n" + " }\n" + " }\n" + " ]\n" + @@ -39,6 +55,9 @@ public void testEqualsWithFixedValue() { RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(json); + Rete rete = ((InternalKnowledgeBase) rulesExecutor.asKieSession().getKieBase()).getRete(); + assertConstraintType(rete, "R1", ConstraintTypeOperator.EQUAL); + List matchedRules = rulesExecutor.processEvents("{ \"i\": 3 }").join(); assertEquals(0, matchedRules.size()); @@ -47,11 +66,33 @@ public void testEqualsWithFixedValue() { matchedRules = rulesExecutor.processEvents("{ \"i\": 3, \"j\": 3 }").join(); assertEquals(1, matchedRules.size()); - assertEquals("r_0", matchedRules.get(0).getRule().getName()); + assertEquals("R1", matchedRules.get(0).getRule().getName()); rulesExecutor.dispose(); } + private void assertConstraintType(Rete rete, String ruleName, ConstraintTypeOperator expectedType) { + boolean asserted = false; + assertEquals("expecting only 1 pkg.", 1, rete.getRuleBase().getPackages().length); + InternalKnowledgePackage ipkg = rete.getRuleBase().getPackages()[0]; + Prototype default_proto = PrototypeFactory.getPrototype(PrototypeFactory.DEFAULT_PROTOTYPE_NAME); + FactTemplate factTemplate = FactFactory.prototypeToFactTemplate(default_proto, ipkg); + EntryPointNode epn = rete.getEntryPointNodes().values().iterator().next(); + ObjectTypeNode otn = epn.getObjectTypeNodes().get(new FactTemplateObjectType(factTemplate)); + ObjectSink[] sinks = otn.getObjectSinkPropagator().getSinks(); + for (ObjectSink objectSink : sinks) { + AlphaNode alphaNode = (AlphaNode) objectSink; + assertEquals("expecting that one rule has one AlphaNode.", 1, alphaNode.getAssociatedRules().length); + Rule rule = alphaNode.getAssociatedRules()[0]; + if (rule.getName().equals(ruleName)) { + IndexableConstraint constraint = (IndexableConstraint) alphaNode.getConstraint(); + assertEquals(expectedType, constraint.getConstraintType()); + asserted = true; + } + } + assertTrue(asserted); + } + @Test public void testEqualsOn2Fields() { String json = diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/LogicalOperatorsTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/LogicalOperatorsTest.java index 8ec94611..7c783063 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/LogicalOperatorsTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/LogicalOperatorsTest.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.drools.ansible.rulebook.integration.api.domain.RuleMatch; import org.drools.ansible.rulebook.integration.api.domain.RulesSet; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpressionUtils; import org.drools.model.Index; import org.drools.model.PrototypeFact; import org.junit.Test; @@ -351,8 +352,8 @@ public void testNegate() { public void testAny() { RulesSet rulesSet = new RulesSet(); rulesSet.addRule().withCondition().any() - .addSingleCondition(prototypeField("i"), Index.ConstraintType.EQUAL, fixedValue(0)).withPatternBinding("event") - .addSingleCondition(prototypeField("i"), Index.ConstraintType.EQUAL, fixedValue(1)).withPatternBinding("event"); + .addSingleCondition(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor("i"), Index.ConstraintType.EQUAL, fixedValue(0)).withPatternBinding("event") + .addSingleCondition(ExtractorPrototypeExpressionUtils.prototypeFieldExtractor("i"), Index.ConstraintType.EQUAL, fixedValue(1)).withPatternBinding("event"); RulesExecutor rulesExecutor = RulesExecutorFactory.createRulesExecutor(rulesSet); checkAnyExecution(rulesExecutor); diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceAfterTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceAfterTest.java index 3eeb0e4e..33ef6096 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceAfterTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceAfterTest.java @@ -2,6 +2,8 @@ import org.drools.ansible.rulebook.integration.api.domain.temporal.TimeAmount; import org.drools.ansible.rulebook.integration.api.rulesmodel.RulesModelUtil; +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorParser; +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorUtils; import org.drools.base.facttemplates.Fact; import org.junit.Test; import org.kie.api.runtime.rule.Match; @@ -96,12 +98,12 @@ public void testOnceAfterWithOr() { for (int i = 0; i < 3; i++) { Fact fact = (Fact) matchedRules.get(0).getDeclarationValue("m_" + i); - String host = fact.get("meta.hosts").toString(); + String host = evalAgainstFact(fact, "meta.hosts").toString(); assertTrue( host.equals( "h1" ) || host.equals( "h2" ) ); - String level = fact.get("alert.level").toString(); + String level = evalAgainstFact(fact, "alert.level").toString(); assertTrue( level.equals( "error" ) || level.equals( "warning" ) ); - Map map = (Map) fact.get(RulesModelUtil.ORIGINAL_MAP_FIELD); + Map map = (Map) fact.asMap(); Map ruleEngineMeta = (Map) ((Map)map.get(RulesModelUtil.META_FIELD)).get(RulesModelUtil.RULE_ENGINE_META_FIELD); assertEquals( new TimeAmount(10, TimeUnit.SECONDS).toString(), ruleEngineMeta.get("once_after_time_window") ); assertEquals( i == 0 ? 2 : 1, ruleEngineMeta.get("events_in_window") ); @@ -116,12 +118,12 @@ public void testOnceAfterWithOr() { assertEquals("r1", matchedRules.get(0).getRule().getName()); Fact fact = (Fact) matchedRules.get(0).getDeclarationValue("m"); - String host = fact.get("meta.hosts").toString(); + String host = evalAgainstFact(fact, "meta.hosts").toString(); assertTrue(host.equals("h1")); - String level = fact.get("alert.level").toString(); + String level = evalAgainstFact(fact, "alert.level").toString(); assertTrue(level.equals("warning")); - Map map = (Map) fact.get(RulesModelUtil.ORIGINAL_MAP_FIELD); + Map map = (Map) fact.asMap(); Map ruleEngineMeta = (Map) ((Map)map.get(RulesModelUtil.META_FIELD)).get(RulesModelUtil.RULE_ENGINE_META_FIELD); assertEquals( new TimeAmount(10, TimeUnit.SECONDS).toString(), ruleEngineMeta.get("once_after_time_window") ); assertEquals( 1, ruleEngineMeta.get("events_in_window") ); @@ -129,4 +131,8 @@ public void testOnceAfterWithOr() { rulesExecutor.dispose(); } + + private static Object evalAgainstFact(Fact fact, String expr) { + return ExtractorUtils.getValueFrom(ExtractorParser.parse(expr), fact.asMap()); + } } diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceWithinTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceWithinTest.java index ca5ac43c..cef579d8 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceWithinTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/OnceWithinTest.java @@ -122,7 +122,7 @@ private void onceWithinTest(String json) { assertEquals(1, matchedRules.size()); Fact fact = (Fact) matchedRules.get(0).getDeclarationValue("singleton"); - Map map = (Map) fact.get(RulesModelUtil.ORIGINAL_MAP_FIELD); + Map map = (Map) fact.asMap(); Map ruleEngineMeta = (Map) ((Map)map.get(RulesModelUtil.META_FIELD)).get(RulesModelUtil.RULE_ENGINE_META_FIELD); assertEquals( new TimeAmount(10, TimeUnit.SECONDS).toString(), ruleEngineMeta.get("once_within_time_window") ); diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/TimedOutTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/TimedOutTest.java index 250e89b2..fc7f385c 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/TimedOutTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/TimedOutTest.java @@ -182,7 +182,7 @@ public void testSimpleTimedOut() { assertEquals(1, stats.getEventsProcessed()); assertEquals(0, stats.getEventsSuppressed()); } - + @Test public void testTimedOutWithAutomaticClockAdvance() throws IOException { String json = diff --git a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraintTest.java b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraintTest.java index 6b6557b8..89a7fd94 100644 --- a/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraintTest.java +++ b/drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/domain/constraints/SelectConstraintTest.java @@ -23,6 +23,7 @@ import org.drools.ansible.rulebook.integration.api.domain.RuleGenerationContext; import org.drools.ansible.rulebook.integration.api.rulesmodel.ParsedCondition; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ExtractorPrototypeExpression; import org.drools.model.PrototypeExpression; import org.junit.Test; @@ -42,8 +43,8 @@ public void createParsedConditionWithSelectExpression() { ParsedCondition parsedCondition = SelectConstraint.INSTANCE.createParsedCondition(new RuleGenerationContext(), SelectConstraint.EXPRESSION_NAME, map); - assertThat(parsedCondition.getLeft()).isInstanceOf(PrototypeExpression.PrototypeFieldValue.class); - assertThat(((PrototypeExpression.PrototypeFieldValue)parsedCondition.getLeft()).getFieldName()).isEqualTo("levels"); + assertThat(parsedCondition.getLeft()).isInstanceOf(ExtractorPrototypeExpression.class); + assertThat(parsedCondition.getLeft().getIndexingKey()).isPresent().contains("levels"); assertThat(parsedCondition.getRight()).isInstanceOf(PrototypeExpression.FixedValue.class); // rightValue is not important. "25" is already incorporated in the operator @@ -65,8 +66,8 @@ public void createParsedConditionWithSelectNotExpression() { ParsedCondition parsedCondition = SelectConstraint.INSTANCE.createParsedCondition(new RuleGenerationContext(), SelectConstraint.NEGATED_EXPRESSION_NAME, map); - assertThat(parsedCondition.getLeft()).isInstanceOf(PrototypeExpression.PrototypeFieldValue.class); - assertThat(((PrototypeExpression.PrototypeFieldValue)parsedCondition.getLeft()).getFieldName()).isEqualTo("levels"); + assertThat(parsedCondition.getLeft()).isInstanceOf(ExtractorPrototypeExpression.class); + assertThat(parsedCondition.getLeft().getIndexingKey()).isPresent().contains("levels"); assertThat(parsedCondition.getRight()).isInstanceOf(PrototypeExpression.FixedValue.class); // rightValue is not important. "25" is already incorporated in the operator diff --git a/drools-ansible-rulebook-integration-durable/src/main/java/org/drools/ansible/rulebook/integration/durable/domain/DurableRule.java b/drools-ansible-rulebook-integration-durable/src/main/java/org/drools/ansible/rulebook/integration/durable/domain/DurableRule.java index 4289688c..5f146e72 100644 --- a/drools-ansible-rulebook-integration-durable/src/main/java/org/drools/ansible/rulebook/integration/durable/domain/DurableRule.java +++ b/drools-ansible-rulebook-integration-durable/src/main/java/org/drools/ansible/rulebook/integration/durable/domain/DurableRule.java @@ -205,22 +205,22 @@ private SimpleCondition createCondition(String binding, String leftValue, String String leftBinding = getBindingFromMap(l); if (leftBinding != null) { - PrototypeExpression rightExpression = prototypeField(((Map) l).get(leftBinding).toString()).composeWith(decodeBinaryOperator(rightKey), toPrototypeExpression(r)); - return new BetaExpressionCondition(binding, prototypeField(leftValue), decodeConstraintType(decodedOp), leftBinding, rightExpression); + PrototypeExpression rightExpression = ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(((Map) l).get(leftBinding).toString()).composeWith(decodeBinaryOperator(rightKey), toPrototypeExpression(r)); + return new BetaExpressionCondition(binding, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftValue), decodeConstraintType(decodedOp), leftBinding, rightExpression); } String rightBinding = getBindingFromMap(r); if (rightBinding != null) { - PrototypeExpression rightExpression = toPrototypeExpression(l).composeWith(decodeBinaryOperator(rightKey), prototypeField(((Map) r).get(rightBinding).toString())); - return new BetaExpressionCondition(binding, prototypeField(leftValue), decodeConstraintType(decodedOp), rightBinding, rightExpression); + PrototypeExpression rightExpression = toPrototypeExpression(l).composeWith(decodeBinaryOperator(rightKey), ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(((Map) r).get(rightBinding).toString())); + return new BetaExpressionCondition(binding, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftValue), decodeConstraintType(decodedOp), rightBinding, rightExpression); } PrototypeExpression rightExpression = toPrototypeExpression(l).composeWith(decodeBinaryOperator(rightKey), toPrototypeExpression(r)); - return new ExpressionCondition(binding, prototypeField(leftValue), decodeConstraintType(decodedOp), rightExpression); + return new ExpressionCondition(binding, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftValue), decodeConstraintType(decodedOp), rightExpression); } if (existingBindings.contains(rightKey)) { if (rightValue instanceof String) { - return new BetaExpressionCondition(binding, prototypeField(leftValue), decodeConstraintType(decodedOp), rightKey, prototypeField((String) rightValue)); + return new BetaExpressionCondition(binding, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(leftValue), decodeConstraintType(decodedOp), rightKey, ExtractorPrototypeExpressionUtils.prototypeFieldExtractor((String) rightValue)); } throw new UnsupportedOperationException(); } @@ -234,7 +234,7 @@ private PrototypeExpression toPrototypeExpression(Object value) { assert(map.size() == 1); String fieldName = (String) map.get("$m"); assert(fieldName != null); - return prototypeField(fieldName); + return ExtractorPrototypeExpressionUtils.prototypeFieldExtractor(fieldName); } return fixedValue(value); } diff --git a/drools-ansible-rulebook-integration-protoextractor/input.txt b/drools-ansible-rulebook-integration-protoextractor/input.txt new file mode 100644 index 00000000..c8a152e5 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/input.txt @@ -0,0 +1 @@ +a.b["c"].d["e/asd"]['f'].g.h \ No newline at end of file diff --git a/drools-ansible-rulebook-integration-protoextractor/pom.xml b/drools-ansible-rulebook-integration-protoextractor/pom.xml new file mode 100644 index 00000000..374e99ae --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/pom.xml @@ -0,0 +1,96 @@ + + + + drools-ansible-rulebook-integration + org.drools + 1.0.3-SNAPSHOT + + 4.0.0 + + drools-ansible-rulebook-integration-protoextractor + + Drools :: Ansible Rulebook Integration :: Protoextractor + + + + org.drools + drools-canonical-model + ${version.drools} + + + org.drools + drools-core + ${version.drools} + + + org.antlr + antlr4-runtime + ${version.org.antlr4} + + + com.fasterxml.jackson.core + jackson-databind + ${version.jackson} + + + org.drools + drools-model-compiler + ${version.drools} + test + + + + + + + org.antlr + antlr4-maven-plugin + ${version.org.antlr4} + + + + antlr4 + + + true + false + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + TestRigGui + none + + java + + + + + org.antlr.v4.gui.TestRig + + org.drools.ansible.rulebook.integration.protoextractor.Protoextractor + extractor + -gui + input.txt + + true + + + + org.antlr + antlr4 + ${version.org.antlr4} + + + + + + diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/antlr4/org/drools/ansible/rulebook/integration/protoextractor/Protoextractor.g4 b/drools-ansible-rulebook-integration-protoextractor/src/main/antlr4/org/drools/ansible/rulebook/integration/protoextractor/Protoextractor.g4 new file mode 100644 index 00000000..80e07683 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/antlr4/org/drools/ansible/rulebook/integration/protoextractor/Protoextractor.g4 @@ -0,0 +1,26 @@ +grammar Protoextractor; + +extractor : identifier ( chunk )* ; + +chunk : ('.' identifier) | squaredAccessor | indexAccessor ; + +squaredAccessor : '[' stringLiteral ']'; +indexAccessor : '[' integerLiteral ']'; + +stringLiteral : STRING1 | STRING2 ; +integerLiteral : SIGNED_INT ; + +// keep as := ID given this is matching upstream pyparsing, see lexer rule. +identifier : ID ; + +// ref https://pyparsing-docs.readthedocs.io/en/latest/pyparsing.html#pyparsing.pyparsing_common.identifier +ID : [\p{Letter}_][\p{Letter}_0-9]* ; + +// ref https://github.com/ansible/ansible-rulebook/blob/24444bfcefcd7e9551d708723b76f58c3de9e976/ansible_rulebook/condition_parser.py#L118-L123 +STRING1 : '\'' ~('\r' | '\n' | '\'')* '\'' ; +STRING2 : '"' ~('\r' | '\n' | '"')* '"' ; + +SIGNED_INT : (MINUS|PLUS)? DIGITS; +MINUS : '-'; +PLUS : '+'; +DIGITS : [0-9]+; diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ASTProductionVisitor.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ASTProductionVisitor.java new file mode 100644 index 00000000..8a51b601 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ASTProductionVisitor.java @@ -0,0 +1,69 @@ +package org.drools.ansible.rulebook.integration.protoextractor; + +import java.util.ArrayList; +import java.util.List; + +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.ChunkContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.ExtractorContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.IdentifierContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.IndexAccessorContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.IntegerLiteralContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.SquaredAccessorContext; +import org.drools.ansible.rulebook.integration.protoextractor.ProtoextractorParser.StringLiteralContext; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ASTBuilderFactory; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ASTNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.IntegerLiteralNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.TextValue; + +public class ASTProductionVisitor extends ProtoextractorBaseVisitor { + + @Override + public ASTNode visitStringLiteral(StringLiteralContext ctx) { + return ASTBuilderFactory.newStringLiteralNode(ctx); + } + + @Override + public ASTNode visitIdentifier(IdentifierContext ctx) { + return ASTBuilderFactory.newIdentifierNode(ctx); + } + + @Override + public ASTNode visitIntegerLiteral(IntegerLiteralContext ctx) { + return ASTBuilderFactory.newIntegerLiteralNode(ctx); + } + + @Override + public ASTNode visitSquaredAccessor(SquaredAccessorContext ctx) { + TextValue str = (TextValue) ctx.stringLiteral().accept(this); + return ASTBuilderFactory.newSquaredAccessorNode(ctx, str); + } + + @Override + public ASTNode visitIndexAccessor(IndexAccessorContext ctx) { + IntegerLiteralNode idx = (IntegerLiteralNode) ctx.integerLiteral().accept(this); + return ASTBuilderFactory.newIndexAccessorNode(ctx, idx); + } + + @Override + public ASTNode visitChunk(ChunkContext ctx) { + if (ctx.identifier() != null) { + return ctx.identifier().accept(this); + } else if (ctx.squaredAccessor() != null) { + return ctx.squaredAccessor().accept(this); + } else if (ctx.indexAccessor() != null) { + return ctx.indexAccessor().accept(this); + } + throw new IllegalStateException("reached illegal state while parsing"); + } + + @Override + public ASTNode visitExtractor(ExtractorContext ctx) { + List values = new ArrayList<>(); + ASTNode value0 = ctx.identifier().accept(this); + values.add(value0); + for (ChunkContext chunk : ctx.chunk()) { + values.add(chunk.accept(this)); + } + return ASTBuilderFactory.newExtractorNode(ctx, values); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorParser.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorParser.java new file mode 100644 index 00000000..8599e76f --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorParser.java @@ -0,0 +1,24 @@ +package org.drools.ansible.rulebook.integration.protoextractor; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; + +public class ExtractorParser { + private ExtractorParser() { + // only static methods. + } + + public static ExtractorNode parse(String expression) { + CodePointCharStream charStream = CharStreams.fromString(expression); + ProtoextractorLexer lexer = new ProtoextractorLexer(charStream); + CommonTokenStream tokens = new CommonTokenStream(lexer); + ProtoextractorParser parser = new ProtoextractorParser(tokens); + ParseTree tree = parser.extractor(); + ASTProductionVisitor visitor = new ASTProductionVisitor(); + ExtractorNode extractor = (ExtractorNode) visitor.visit(tree); + return extractor; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorUtils.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorUtils.java new file mode 100644 index 00000000..e2d0c508 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorUtils.java @@ -0,0 +1,21 @@ +package org.drools.ansible.rulebook.integration.protoextractor; + +import java.util.List; + +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.NormalizedFieldRepresentationVisitor; +import org.drools.ansible.rulebook.integration.protoextractor.prototype.ValueExtractionVisitor; + +public class ExtractorUtils { + private ExtractorUtils() { + // only static methods. + } + + public static List getParts(ExtractorNode extractorNode) { + return new NormalizedFieldRepresentationVisitor().visit(extractorNode); + } + + public static Object getValueFrom(ExtractorNode extractorNode, Object readValue) { + return new ValueExtractionVisitor(readValue).visit(extractorNode); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTBuilderFactory.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTBuilderFactory.java new file mode 100644 index 00000000..0aa96bc1 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTBuilderFactory.java @@ -0,0 +1,29 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import java.util.List; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class ASTBuilderFactory { + private ASTBuilderFactory() { + // only static utils methods. + } + public static StringLiteralNode newStringLiteralNode(ParserRuleContext ctx) { + return new StringLiteralNode( ctx ); + } + public static IntegerLiteralNode newIntegerLiteralNode(ParserRuleContext ctx) { + return new IntegerLiteralNode( ctx ); + } + public static IdentifierNode newIdentifierNode(ParserRuleContext ctx) { + return new IdentifierNode( ctx ); + } + public static SquaredAccessorNode newSquaredAccessorNode(ParserRuleContext ctx, TextValue value) { + return new SquaredAccessorNode( ctx, value ); + } + public static IndexAccessorNode newIndexAccessorNode(ParserRuleContext ctx, IntegerLiteralNode value) { + return new IndexAccessorNode( ctx, value ); + } + public static ExtractorNode newExtractorNode(ParserRuleContext ctx, List values) { + return new ExtractorNode( ctx, values ); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTNode.java new file mode 100644 index 00000000..3532a271 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ASTNode.java @@ -0,0 +1,19 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +public interface ASTNode { + int getStartChar(); + + int getEndChar(); + + int getStartLine(); + + int getStartColumn(); + + int getEndLine(); + + int getEndColumn(); + + String getText(); + + T accept(Visitor v); +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/AbstractNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/AbstractNode.java new file mode 100644 index 00000000..89781df4 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/AbstractNode.java @@ -0,0 +1,117 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; + +public abstract class AbstractNode implements ASTNode { + protected final ASTNode[] EMPTY_CHILDREN = new ASTNode[0]; + private int startChar; + private int endChar; + private int startLine; + private int startColumn; + private int endLine; + private int endColumn; + + private String text; + + protected AbstractNode( AbstractNode usingCtxFrom ) { + copyLocationAttributesFrom(usingCtxFrom); + } + + public AbstractNode( ParserRuleContext ctx ) { + // DO NOT keep the reference to `ParserRuleContext` to avoid unneeded retention of lexer structures. + this.setStartChar( ctx.getStart().getStartIndex() ); + this.setStartLine( ctx.getStart().getLine() ); + this.setStartColumn( ctx.getStart().getCharPositionInLine() ); + this.setEndChar( ctx.getStop().getStopIndex() ); + this.setEndLine( ctx.getStop().getLine() ); + this.setEndColumn( ctx.getStop().getCharPositionInLine() + ctx.getStop().getText().length() ); + this.setText( getOriginalText( ctx ) ); + } + + private AbstractNode copyLocationAttributesFrom(AbstractNode from) { + this.setStartChar(from.getStartChar()); + this.setStartLine(from.getStartLine()); + this.setStartColumn(from.getStartColumn()); + this.setEndChar(from.getEndChar()); + this.setEndLine(from.getEndLine()); + this.setEndColumn(from.getEndColumn()); + this.setText(from.getText()); + return this; + } + + public static String getOriginalText(ParserRuleContext ctx) { + int a = ctx.start.getStartIndex(); + int b = ctx.stop.getStopIndex(); + Interval interval = new Interval( a, b ); + return ctx.getStart().getInputStream().getText( interval ); + } + + @Override + public int getStartChar() { + return startChar; + } + + public void setStartChar(int startChar) { + this.startChar = startChar; + } + + @Override + public int getEndChar() { + return endChar; + } + + public void setEndChar(int endChar) { + this.endChar = endChar; + } + + @Override + public int getStartLine() { + return startLine; + } + + public void setStartLine(int startLine) { + this.startLine = startLine; + } + + @Override + public int getStartColumn() { + return startColumn; + } + + public void setStartColumn(int startColumn) { + this.startColumn = startColumn; + } + + @Override + public int getEndLine() { + return endLine; + } + + public void setEndLine(int endLine) { + this.endLine = endLine; + } + + @Override + public int getEndColumn() { + return endColumn; + } + + public void setEndColumn(int endColumn) { + this.endColumn = endColumn; + } + + @Override + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + return getClass().getSimpleName()+"{" + text + "}"; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/DefaultedVisitor.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/DefaultedVisitor.java new file mode 100644 index 00000000..d4ccf913 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/DefaultedVisitor.java @@ -0,0 +1,20 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +public abstract class DefaultedVisitor implements Visitor { + public abstract T defaultVisit(ASTNode n); + public T visit(ASTNode n) { + return defaultVisit(n); + } + public T visit(IdentifierNode n) { + return defaultVisit(n); + } + public T visit(SquaredAccessorNode n) { + return defaultVisit(n); + } + public T visit(IndexAccessorNode n) { + return defaultVisit(n); + } + public T visit(ExtractorNode n) { + return defaultVisit(n); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ExtractorNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ExtractorNode.java new file mode 100644 index 00000000..7fb0d4f8 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/ExtractorNode.java @@ -0,0 +1,42 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class ExtractorNode extends AbstractNode { + private final List values; + + public ExtractorNode(ParserRuleContext ctx, List values2) { + super(ctx); + Objects.requireNonNull(values2); + this.values = Collections.unmodifiableList(values2); + } + + private ExtractorNode(ExtractorNode extractorNode, List skippingFirst) { + super(extractorNode); + Objects.requireNonNull(skippingFirst); + this.values = Collections.unmodifiableList(skippingFirst); + } + + public ExtractorNode cloneSkipFirst() { + final List skippingFirst = values.subList(1, values.size()); + return new ExtractorNode(this, skippingFirst); + } + + public List getValues() { + return values; + } + + @Override + public String toString() { + return "ExtractorNode [values=" + values + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IdentifierNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IdentifierNode.java new file mode 100644 index 00000000..9cea5867 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IdentifierNode.java @@ -0,0 +1,28 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class IdentifierNode extends AbstractNode implements TextValue { + + private final String value; + + public IdentifierNode(ParserRuleContext ctx) { + super(ctx); + this.value = getOriginalText(ctx); + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + return "IdentifierNode [value=" + value + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IndexAccessorNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IndexAccessorNode.java new file mode 100644 index 00000000..7080c5aa --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IndexAccessorNode.java @@ -0,0 +1,29 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import java.util.Objects; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class IndexAccessorNode extends AbstractNode { + private final IntegerLiteralNode value; + + public IndexAccessorNode(ParserRuleContext ctx, IntegerLiteralNode value) { + super(ctx); + Objects.requireNonNull(value); + this.value = value; + } + + public Integer getValue() { + return this.value.getValue(); + } + + @Override + public String toString() { + return "IndexAccessorNode [value=" + value + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IntegerLiteralNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IntegerLiteralNode.java new file mode 100644 index 00000000..b7937a99 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/IntegerLiteralNode.java @@ -0,0 +1,28 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class IntegerLiteralNode extends AbstractNode { + + private final Integer value; + + public IntegerLiteralNode(ParserRuleContext ctx) { + super(ctx); + String originalText = getOriginalText(ctx); + this.value = Integer.valueOf(originalText); + } + + public Integer getValue() { + return value; + } + + @Override + public String toString() { + return "IntegerLiteralNode [value=" + value + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/SquaredAccessorNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/SquaredAccessorNode.java new file mode 100644 index 00000000..19feae39 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/SquaredAccessorNode.java @@ -0,0 +1,29 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import java.util.Objects; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class SquaredAccessorNode extends AbstractNode { + private final TextValue value; + + public SquaredAccessorNode(ParserRuleContext ctx, TextValue value) { + super(ctx); + Objects.requireNonNull(value); + this.value = value; + } + + public String getValue() { + return this.value.getValue(); + } + + @Override + public String toString() { + return "SquaredAccessorNode [value=" + value + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/StringLiteralNode.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/StringLiteralNode.java new file mode 100644 index 00000000..1693dba6 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/StringLiteralNode.java @@ -0,0 +1,29 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +import org.antlr.v4.runtime.ParserRuleContext; + +public class StringLiteralNode extends AbstractNode implements TextValue { + + private final String value; + + public StringLiteralNode(ParserRuleContext ctx) { + super(ctx); + String originalText = getOriginalText(ctx); + this.value = originalText.substring(1, originalText.length()-1); + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + return "StringLiteralNode [value=" + value + "]"; + } + + @Override + public T accept(Visitor v) { + return v.visit(this); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/TextValue.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/TextValue.java new file mode 100644 index 00000000..225caff5 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/TextValue.java @@ -0,0 +1,7 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +public interface TextValue { + + String getValue(); + +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/Visitor.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/Visitor.java new file mode 100644 index 00000000..28a74890 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/ast/Visitor.java @@ -0,0 +1,9 @@ +package org.drools.ansible.rulebook.integration.protoextractor.ast; + +public interface Visitor { + T visit(ASTNode n); + T visit(IdentifierNode n); + T visit(SquaredAccessorNode n); + T visit(IndexAccessorNode n); + T visit(ExtractorNode n); +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/CompleteExtractorPrototypeExpression.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/CompleteExtractorPrototypeExpression.java new file mode 100644 index 00000000..7a9e3406 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/CompleteExtractorPrototypeExpression.java @@ -0,0 +1,44 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.model.PrototypeExpression.EvaluableExpression; +import org.drools.model.PrototypeFact; +import org.drools.model.PrototypeVariable; + +public class CompleteExtractorPrototypeExpression extends ExtractorPrototypeExpression implements EvaluableExpression { + private final PrototypeVariable protoVar; + + public CompleteExtractorPrototypeExpression(PrototypeVariable protoVar, ExtractorNode extractorNode) { + super(extractorNode); + this.protoVar = protoVar; + } + + public PrototypeVariable getProtoVar() { + return protoVar; + } + + @Override + public boolean hasPrototypeVariable() { + return true; + } + + @Override + public Collection getPrototypeVariables() { + return Collections.singletonList(protoVar); + } + + @Override + public Object evaluate(Map factsMap) { + PrototypeFact prototypeFact = factsMap.get(protoVar); + return asFunction(IGNORED).apply(prototypeFact); + } + + @Override + public String toString() { + return "CompleteExtractorPrototypeExpression{" + extractorNode + "}"; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpression.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpression.java new file mode 100644 index 00000000..7d663e88 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpression.java @@ -0,0 +1,58 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorUtils; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.base.facttemplates.Fact; +import org.drools.model.Prototype; +import org.drools.model.PrototypeDSL; +import org.drools.model.PrototypeExpression; +import org.drools.model.PrototypeFact; +import org.drools.model.functions.Function1; + +public class ExtractorPrototypeExpression implements PrototypeExpression { + /** + * This is okay for ansible-integration work as prototype do not define accessor programmatically, + * but having prototype definition ignored in {@link #asFunction(Prototype)} call, ought to be revised + * if porting this module into Drools and generalizing beyond usage in ansible-integration. + */ + public static final Prototype IGNORED = PrototypeDSL.prototype("IGNORED"); + protected final ExtractorNode extractorNode; + protected final String computedFieldName; + protected final Collection computedImpactedFields; + + public ExtractorPrototypeExpression(ExtractorNode extractorNode) { + this.extractorNode = extractorNode; + this.computedFieldName = ExtractorUtils.getParts(extractorNode).stream().collect(Collectors.joining()); + this.computedImpactedFields = Collections.singletonList(ExtractorUtils.getParts(extractorNode).get(0)); + } + + @Override + public Function1 asFunction(Prototype prototype) { + return pf -> { + Map asMap = ((Fact) pf).asMap(); + Object value = ExtractorUtils.getValueFrom(extractorNode, asMap); + return value; + }; + } + + @Override + public Optional getIndexingKey() { + return Optional.of(this.computedFieldName); + } + + @Override + public String toString() { + return "ExtractorPrototypeExpression{" + extractorNode + "}"; + } + + @Override + public Collection getImpactedFields() { + return this.computedImpactedFields; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpressionUtils.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpressionUtils.java new file mode 100644 index 00000000..51aa23f0 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ExtractorPrototypeExpressionUtils.java @@ -0,0 +1,33 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorParser; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.model.PrototypeExpression; +import org.drools.model.PrototypeVariable; + +public class ExtractorPrototypeExpressionUtils { + private ExtractorPrototypeExpressionUtils() { + // only static methods. + } + + public static ExtractorPrototypeExpression prototypeFieldExtractor(String expression) { + ExtractorNode parse = ExtractorParser.parse(expression); + return new ExtractorPrototypeExpression(parse); + } + + /** + * old way of: `prototypeField(rightField.substring(dotPos+1))` + * given: m_0.a.b.c + * returns an extractor for: a.b.c + */ + public static ExtractorPrototypeExpression prototypeFieldExtractorSkippingFirst(String expression) { + ExtractorNode parse = ExtractorParser.parse(expression); + ExtractorNode parseSkipFirst = parse.cloneSkipFirst(); + return new ExtractorPrototypeExpression(parseSkipFirst); + } + + public static PrototypeExpression prototypeFieldExtractor(PrototypeVariable betaVariable, String expression) { + ExtractorNode parse = ExtractorParser.parse(expression); + return new CompleteExtractorPrototypeExpression(betaVariable, parse); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/NormalizedFieldRepresentationVisitor.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/NormalizedFieldRepresentationVisitor.java new file mode 100644 index 00000000..d0558b9b --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/NormalizedFieldRepresentationVisitor.java @@ -0,0 +1,46 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import java.util.ArrayList; +import java.util.List; + +import org.drools.ansible.rulebook.integration.protoextractor.ast.ASTNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.IdentifierNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.IndexAccessorNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.SquaredAccessorNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.Visitor; + +public class NormalizedFieldRepresentationVisitor implements Visitor> { + List result = new ArrayList<>(); + + @Override + public List visit(ASTNode n) { + throw new UnsupportedOperationException("Unimplemented method 'visit'"); + } + + @Override + public List visit(IdentifierNode n) { + result.add(n.getValue()); + return result; + } + + @Override + public List visit(SquaredAccessorNode n) { + result.add(n.getValue()); + return result; + } + + @Override + public List visit(IndexAccessorNode n) { + result.add("("+n.getValue()+")"); + return result; + } + + @Override + public List visit(ExtractorNode n) { + for (ASTNode chunk : n.getValues()) { + chunk.accept(this); + } + return result; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ValueExtractionVisitor.java b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ValueExtractionVisitor.java new file mode 100644 index 00000000..598e3de5 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/main/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/ValueExtractionVisitor.java @@ -0,0 +1,79 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import java.util.List; +import java.util.Map; + +import org.drools.ansible.rulebook.integration.protoextractor.ast.ASTNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.DefaultedVisitor; +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.IdentifierNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.IndexAccessorNode; +import org.drools.ansible.rulebook.integration.protoextractor.ast.SquaredAccessorNode; +import org.drools.model.Prototype; + +public class ValueExtractionVisitor extends DefaultedVisitor { + private final Object original; + private Object cur; + + public ValueExtractionVisitor(Object original) { + this.original = original; + this.cur = this.original; + } + + @Override + public Object defaultVisit(ASTNode n) { + throw new UnsupportedOperationException("this visitor implemented all visit methods"); + } + + @Override + public Object visit(IdentifierNode n) { + cur = fromMap(cur, n.getValue()); + return cur; + } + + private static Object fromMap(Object in, String key) { + if (in instanceof Map) { + Map theMap = (Map) in; + if (theMap.containsKey(key)) { + return theMap.get(key); + } else { + return Prototype.UNDEFINED_VALUE; + } + } else { + return Prototype.UNDEFINED_VALUE; + } + } + + @Override + public Object visit(SquaredAccessorNode n) { + cur = fromMap(cur, n.getValue()); + return cur; + } + + @Override + public Object visit(IndexAccessorNode n) { + if (cur instanceof List) { + List theList = (List) cur; + int javaIdx = n.getValue() >= 0 ? n.getValue() : theList.size() + n.getValue(); + if (javaIdx < theList.size()) { // avoid index out of bounds; + cur = theList.get(javaIdx); + } else { + cur = Prototype.UNDEFINED_VALUE; + } + } else { + cur = Prototype.UNDEFINED_VALUE; + } + return cur; + } + + @Override + public Object visit(ExtractorNode n) { + for (ASTNode chunk : n.getValues()) { + if (this.cur == null || this.cur == Prototype.UNDEFINED_VALUE) { + break; + } + this.cur = chunk.accept(this); + } + return cur; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorTest.java b/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorTest.java new file mode 100644 index 00000000..5afcd262 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/ExtractorTest.java @@ -0,0 +1,100 @@ +package org.drools.ansible.rulebook.integration.protoextractor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; + +import org.drools.ansible.rulebook.integration.protoextractor.ast.ExtractorNode; +import org.drools.model.Prototype; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class ExtractorTest { + + @Test + public void testBasic() throws Exception { + final String expression = "a.b[\"c\"].d[\"e/asd\"]['f'].g.h"; + + ExtractorNode extractor = ExtractorParser.parse(expression); + List parts = ExtractorUtils.getParts(extractor); + assertThat(parts) + .as("a series of string chunks which could be used for indexing") + .containsExactly("a", "b", "c", "d", "e/asd", "f", "g", "h"); + + final String JSON = Files.readString(Paths.get(ExtractorTest.class.getResource("/test1.json").toURI())); + Map readValue = new ObjectMapper().readValue(JSON, Map.class); + Object valueExtracted = ExtractorUtils.getValueFrom(extractor, readValue); + assertThat(valueExtracted) + .as("extractor can be used to extract value based on the path expression") + .isEqualTo(47); + } + + @Test + public void testMixedBag() throws Exception { + final String expression = "range[\"x\"][1][2].a[\"b\"]"; + + ExtractorNode extractor = ExtractorParser.parse(expression); + + final String JSON = Files.readString(Paths.get(ExtractorTest.class.getResource("/mixedBag.json").toURI())); + Map readValue = new ObjectMapper().readValue(JSON, Map.class); + Object valueExtracted = ExtractorUtils.getValueFrom(extractor, readValue); + assertThat(valueExtracted) + .as("extractor can be used to extract value based on the path expression") + .isEqualTo(47); + } + + @Test + public void testMixedBagReverse() throws Exception { + final String expression = "range[\"x\"][-1][2].a[\"b\"]"; + + ExtractorNode extractor = ExtractorParser.parse(expression); + + final String JSON = Files.readString(Paths.get(ExtractorTest.class.getResource("/mixedBag.json").toURI())); + Map readValue = new ObjectMapper().readValue(JSON, Map.class); + Object valueExtracted = ExtractorUtils.getValueFrom(extractor, readValue); + assertThat(valueExtracted) + .as("extractor can be used to extract value based on the path expression") + .isEqualTo(47); + } + + @Test + public void testMixedBagNullRange2() throws Exception { + assertThat(valueFromMixedBag("range2")) + .as("the key `range2` exists as a first level entry in the json, but the value is the null literal") + .isNull(); + } + + @Test + public void testMixedBagUndefFirstLevel() throws Exception { + assertThat(valueFromMixedBag("unexisting") == Prototype.UNDEFINED_VALUE) + .as("this key does not exists at all in the json") + .isTrue(); + } + + @Test + public void testMixedBagArrayIndexExistsButNullValue() throws Exception { + assertThat(valueFromMixedBag("range.x[0]")) + .as("this is accessing index 0 so the first element in a json array of size 2, but the first element in the json array is the null literal") + .isNull(); + } + + @Test + public void testMixedBagArrayIndexUnexisting() throws Exception { + assertThat(valueFromMixedBag("range.x[999]") == Prototype.UNDEFINED_VALUE) + .as("the json array is of size 2, so this 999 index position is undef") + .isTrue(); + } + + private Object valueFromMixedBag(String expression) throws Exception { + ExtractorNode extractor = ExtractorParser.parse(expression); + + final String JSON = Files.readString(Paths.get(ExtractorTest.class.getResource("/mixedBag.json").toURI())); + Map readValue = new ObjectMapper().readValue(JSON, Map.class); + Object valueExtracted = ExtractorUtils.getValueFrom(extractor, readValue); + return valueExtracted; + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/PrototypeTest.java b/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/PrototypeTest.java new file mode 100644 index 00000000..2f9a177f --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/test/java/org/drools/ansible/rulebook/integration/protoextractor/prototype/PrototypeTest.java @@ -0,0 +1,48 @@ +package org.drools.ansible.rulebook.integration.protoextractor.prototype; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; + +import org.drools.ansible.rulebook.integration.protoextractor.ExtractorParser; +import org.drools.base.facttemplates.Fact; +import org.drools.model.PrototypeDSL; +import org.drools.model.PrototypeFact; +import org.drools.modelcompiler.facttemplate.FactFactory; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class PrototypeTest { + + @Test + public void testBasic() throws Exception { + final String expression = "a.b[\"c\"].d[\"e/asd\"]['f'].g.h"; + + ExtractorPrototypeExpression expr = new ExtractorPrototypeExpression(ExtractorParser.parse(expression)); + assertThat(expr.getImpactedFields()) + .as("always the first of the chunks") + .isEqualTo(List.of("a")); + assertThat(expr.getIndexingKey()) + .as("TODO normalization of chunks used for indexing") + .isPresent() + .contains("abcde/asdfgh"); + + final String JSON = Files.readString(Paths.get(PrototypeTest.class.getResource("/test1.json").toURI())); + final Map readValue = new ObjectMapper().readValue(JSON, new TypeReference>() {}); + final Fact mapBasedFact = FactFactory.createMapBasedFact(PrototypeDSL.prototype("test"), readValue); + assertThat(mapBasedFact.get("a")) + .as("sanity check on manually built drools Fact") + .isNotNull() + .isInstanceOf(Map.class); + + Object valueExtracted = expr.asFunction(ExtractorPrototypeExpression.IGNORED).apply((PrototypeFact) mapBasedFact); + assertThat(valueExtracted) + .as("ExtractorPrototypeExpression used to extract value based on the path expression") + .isEqualTo(47); + } +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/test/resources/mixedBag.json b/drools-ansible-rulebook-integration-protoextractor/src/test/resources/mixedBag.json new file mode 100644 index 00000000..68ae4aeb --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/test/resources/mixedBag.json @@ -0,0 +1,17 @@ +{ + "range": { + "x": [ + null, + [ + null, + null, + { + "a": { + "b": 47 + } + } + ] + ] + }, + "range2": null +} diff --git a/drools-ansible-rulebook-integration-protoextractor/src/test/resources/test1.json b/drools-ansible-rulebook-integration-protoextractor/src/test/resources/test1.json new file mode 100644 index 00000000..9ae62c02 --- /dev/null +++ b/drools-ansible-rulebook-integration-protoextractor/src/test/resources/test1.json @@ -0,0 +1,17 @@ +{ + "a": { + "b": { + "c": { + "d": { + "e/asd": { + "f": { + "g": { + "h": 47 + } + } + } + } + } + } + } +} diff --git a/pom.xml b/pom.xml index 023e92a7..cf1984df 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ pom + drools-ansible-rulebook-integration-protoextractor drools-ansible-rulebook-integration-api drools-ansible-rulebook-integration-runtime @@ -33,6 +34,7 @@ 3.20.2 2.13.1 20230227 + 4.10.1 3.0.0-M5 1.35