From 05de24d784661cbd614036d3d697b9ea335f2c7f Mon Sep 17 00:00:00 2001 From: Toni Rikkola Date: Mon, 29 Jan 2024 12:57:19 +0000 Subject: [PATCH] KOGITO-10000 : YaRD: Add YaRD to the released repositories Part of KOGITO-9997 : Create end to end usable YaRD example --- kogito-bom/pom.xml | 22 ++ kogito-build/kogito-dependencies-bom/pom.xml | 18 ++ kogito-build/kogito-kie-bom/pom.xml | 5 + pom.xml | 1 + yard/pom.xml | 69 +++++++ yard/yard-api/pom.xml | 61 ++++++ .../org/kie/yard/api/model/DecisionLogic.java | 31 +++ .../org/kie/yard/api/model/DecisionTable.java | 70 +++++++ .../java/org/kie/yard/api/model/Element.java | 64 ++++++ .../org/kie/yard/api/model/InlineRule.java | 45 ++++ .../java/org/kie/yard/api/model/Input.java | 43 ++++ .../kie/yard/api/model/LiteralExpression.java | 34 +++ .../org/kie/yard/api/model/Operators.java | 49 +++++ .../java/org/kie/yard/api/model/Rule.java | 23 +++ .../kie/yard/api/model/RuleDefSerializer.java | 88 ++++++++ .../org/kie/yard/api/model/WhenThenRule.java | 53 +++++ .../api/model/WhenThenRuleThenSerializer.java | 56 +++++ .../java/org/kie/yard/api/model/YaRD.java | 82 ++++++++ .../src/main/resources/YaRD-schema.json | 191 +++++++++++++++++ yard/yard-core/pom.xml | 75 +++++++ .../org/kie/yard/core/DTableUnitBuilder.java | 195 ++++++++++++++++++ .../main/java/org/kie/yard/core/Firable.java | 27 +++ .../yard/core/LiteralExpressionBuilder.java | 39 ++++ .../core/LiteralExpressionInterpreter.java | 68 ++++++ .../org/kie/yard/core/QuotedExprParsed.java | 114 ++++++++++ .../java/org/kie/yard/core/StoreHandle.java | 69 +++++++ .../yard/core/SyntheticRuleUnitWrapper.java | 39 ++++ .../org/kie/yard/core/YaRDDefinitions.java | 55 +++++ .../java/org/kie/yard/core/YaRDParser.java | 84 ++++++++ .../yard/core/DomesticPackagePricesTest.java | 59 ++++++ .../org/kie/yard/core/ExtraCostsTest.java | 45 ++++ .../kie/yard/core/InsuranceBasePriceTest.java | 58 ++++++ .../test/java/org/kie/yard/core/TestBase.java | 53 +++++ .../resources/domestic-package-prices.yml | 30 +++ .../src/test/resources/extra-costs.yml | 47 +++++ .../test/resources/insurance-base-price.yml | 30 +++ .../src/test/resources/logback-test.xml | 18 ++ .../scorecards/README-health-card.yml | 47 +++++ .../src/test/resources/scorecards/README.md | 8 + .../scorecards/branch-responsibilities.yml | 38 ++++ .../scorecards/drafts/branch-blocked-card.yml | 21 ++ .../scorecards/drafts/closed-prs-card.yml | 23 +++ .../scorecards/drafts/open-prs-card.yml | 21 ++ .../drafts/weekly-branch-health-card.yml | 22 ++ .../scorecards/git-repository-health-card.yml | 56 +++++ .../scorecards/resource-limits-card.yml | 40 ++++ .../src/test/resources/ticket-score-cards.yml | 46 +++++ 47 files changed, 2432 insertions(+) create mode 100644 yard/pom.xml create mode 100644 yard/yard-api/pom.xml create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionLogic.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionTable.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/Element.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/InlineRule.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/Input.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/LiteralExpression.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/Operators.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/Rule.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/RuleDefSerializer.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRule.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRuleThenSerializer.java create mode 100644 yard/yard-api/src/main/java/org/kie/yard/api/model/YaRD.java create mode 100644 yard/yard-api/src/main/resources/YaRD-schema.json create mode 100644 yard/yard-core/pom.xml create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/DTableUnitBuilder.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/Firable.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionBuilder.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionInterpreter.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/QuotedExprParsed.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/StoreHandle.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/SyntheticRuleUnitWrapper.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/YaRDDefinitions.java create mode 100644 yard/yard-core/src/main/java/org/kie/yard/core/YaRDParser.java create mode 100644 yard/yard-core/src/test/java/org/kie/yard/core/DomesticPackagePricesTest.java create mode 100644 yard/yard-core/src/test/java/org/kie/yard/core/ExtraCostsTest.java create mode 100644 yard/yard-core/src/test/java/org/kie/yard/core/InsuranceBasePriceTest.java create mode 100644 yard/yard-core/src/test/java/org/kie/yard/core/TestBase.java create mode 100644 yard/yard-core/src/test/resources/domestic-package-prices.yml create mode 100644 yard/yard-core/src/test/resources/extra-costs.yml create mode 100644 yard/yard-core/src/test/resources/insurance-base-price.yml create mode 100644 yard/yard-core/src/test/resources/logback-test.xml create mode 100644 yard/yard-core/src/test/resources/scorecards/README-health-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/README.md create mode 100644 yard/yard-core/src/test/resources/scorecards/branch-responsibilities.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/drafts/branch-blocked-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/drafts/closed-prs-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/drafts/open-prs-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/drafts/weekly-branch-health-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/git-repository-health-card.yml create mode 100644 yard/yard-core/src/test/resources/scorecards/resource-limits-card.yml create mode 100644 yard/yard-core/src/test/resources/ticket-score-cards.yml diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml index 64c1aeddc2b..994fe02e35c 100755 --- a/kogito-bom/pom.xml +++ b/kogito-bom/pom.xml @@ -2789,6 +2789,28 @@ ${project.version} sources + + org.kie.kogito + yard-api + ${project.version} + + + org.kie.kogito + yard-api + ${project.version} + sources + + + org.kie.kogito + yard-core + ${project.version} + + + org.kie.kogito + yard-core + ${project.version} + sources + diff --git a/kogito-build/kogito-dependencies-bom/pom.xml b/kogito-build/kogito-dependencies-bom/pom.xml index c5d930fd2e8..5ef18a7d500 100644 --- a/kogito-build/kogito-dependencies-bom/pom.xml +++ b/kogito-build/kogito-dependencies-bom/pom.xml @@ -132,6 +132,9 @@ 4.2.0 32.0.0-jre 1.24.0 + 0.4 + 0.4 + 1.1.0 @@ -806,6 +809,21 @@ jep ${version.black.ninia} + + org.treblereel.gwt.yaml.mapper + common + ${version.yaml.mapper} + + + org.treblereel.gwt.yaml.mapper + processor + ${version.yaml.mapper} + + + ch.obermuhlner + jshell-scriptengine + ${version.jshell} + diff --git a/kogito-build/kogito-kie-bom/pom.xml b/kogito-build/kogito-kie-bom/pom.xml index 836f4c4cbd3..e6333d44096 100644 --- a/kogito-build/kogito-kie-bom/pom.xml +++ b/kogito-build/kogito-kie-bom/pom.xml @@ -185,6 +185,11 @@ drools-ruleunits-api ${version.org.kie} + + org.drools + drools-ruleunits-dsl + ${version.org.kie} + org.drools drools-ruleunits-impl diff --git a/pom.xml b/pom.xml index fbe18c88c2e..4366453db1a 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,7 @@ addons kogito-workitems kogito-serverless-workflow + yard kogito-maven-plugin springboot diff --git a/yard/pom.xml b/yard/pom.xml new file mode 100644 index 00000000000..40363e10616 --- /dev/null +++ b/yard/pom.xml @@ -0,0 +1,69 @@ + + + + + 4.0.0 + + + + org.kie.kogito + kogito-build-parent + 999-SNAPSHOT + ../kogito-build/kogito-build-parent/pom.xml + + + yard + pom + + Kogito :: YARD + + A simple way to describe declarative Decisions and Rules in YAML. + + + + 1 + false + + + + yard-api + yard-core + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${surefire.forkCount} + true + hourly + + + + + + + diff --git a/yard/yard-api/pom.xml b/yard/yard-api/pom.xml new file mode 100644 index 00000000000..cf91d94dd21 --- /dev/null +++ b/yard/yard-api/pom.xml @@ -0,0 +1,61 @@ + + + + 4.0.0 + + org.kie.kogito + yard + 999-SNAPSHOT + + + yard-api + + + org.kie.yard.api + + + + + + org.kie.kogito + kogito-kie-bom + ${project.version} + pom + import + + + + + + + org.treblereel.gwt.yaml.mapper + common + + + + org.treblereel.gwt.yaml.mapper + processor + provided + + + \ No newline at end of file diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionLogic.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionLogic.java new file mode 100644 index 00000000000..5513b312884 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionLogic.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import org.treblereel.gwt.yaml.api.annotation.YamlSubtype; +import org.treblereel.gwt.yaml.api.annotation.YamlTypeInfo; + +@YamlTypeInfo( + key = "type", + value = { + @YamlSubtype(alias = "DecisionTable", type = DecisionTable.class), + @YamlSubtype(alias = "LiteralExpression", type = LiteralExpression.class) + }) +public interface DecisionLogic { +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionTable.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionTable.java new file mode 100644 index 00000000000..5a8c1f3d0b0 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/DecisionTable.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.List; + +import org.treblereel.gwt.yaml.api.annotation.YAMLMapper; +import org.treblereel.gwt.yaml.api.annotation.YamlTypeDeserializer; +import org.treblereel.gwt.yaml.api.annotation.YamlTypeSerializer; + +@YAMLMapper +public class DecisionTable implements DecisionLogic { + private List inputs; + private String hitPolicy = "ANY"; + @Deprecated + private List outputComponents; + @YamlTypeSerializer(RuleDefSerializer.class) + @YamlTypeDeserializer(RuleDefSerializer.class) + private List rules; + + public void setInputs(List inputs) { + this.inputs = inputs; + } + + public void setOutputComponents(List outputComponents) { + this.outputComponents = outputComponents; + } + + public List getInputs() { + return inputs; + } + + @Deprecated + public List getOutputComponents() { + return outputComponents; + } + + public String getHitPolicy() { + return hitPolicy; + } + + public void setHitPolicy(String hitPolicy) { + this.hitPolicy = hitPolicy; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/Element.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/Element.java new file mode 100644 index 00000000000..a0879e9856d --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/Element.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.List; + +import org.treblereel.gwt.yaml.api.annotation.YAMLMapper; + +@YAMLMapper +public class Element { + + private String name; + private String type; + private List requirements; + private DecisionLogic logic; + + public void setName(String name) { + this.name = name; + } + + public void setType(String type) { + this.type = type; + } + + public void setRequirements(List requirements) { + this.requirements = requirements; + } + + public void setLogic(DecisionLogic logic) { + this.logic = logic; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public List getRequirements() { + return requirements; + } + + public DecisionLogic getLogic() { + return logic; + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/InlineRule.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/InlineRule.java new file mode 100644 index 00000000000..79c921e15ee --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/InlineRule.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.List; + +public class InlineRule implements Rule { + + private final int rowNumber; + public List def; + + public InlineRule(int rowNumber, List data) { + this.rowNumber = rowNumber; + this.def = data; + } + + @Override + public int getRowNumber() { + return rowNumber; + } + + public List getDef() { + return def; + } + + public void setDef(List def) { + this.def = def; + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/Input.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/Input.java new file mode 100644 index 00000000000..729b56c10fb --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/Input.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import org.treblereel.gwt.yaml.api.annotation.YAMLMapper; + +@YAMLMapper +public class Input { + private String name; + private String type; + + public void setName(String name) { + this.name = name; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } +} \ No newline at end of file diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/LiteralExpression.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/LiteralExpression.java new file mode 100644 index 00000000000..b3f04698041 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/LiteralExpression.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import org.treblereel.gwt.yaml.api.annotation.YAMLMapper; + +@YAMLMapper +public class LiteralExpression implements DecisionLogic { + private String expression; + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return expression; + } +} \ No newline at end of file diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/Operators.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/Operators.java new file mode 100644 index 00000000000..2b7aa158ec0 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/Operators.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.Objects; + +/** + * Interface instead of enum due to possible custom operators. + */ +public interface Operators { + String NOT_EQUALS = "!="; + String EQUALS = "="; + String GREATER_OR_EQUAL = ">="; + String GREATER_THAN = ">"; + String LESS_OR_EQUAL = "<="; + String LESS_THAN = "<"; + + String[] ALL = { EQUALS, LESS_OR_EQUAL, LESS_THAN, GREATER_OR_EQUAL, GREATER_THAN, NOT_EQUALS }; + + static int compare(final String operator, + final String other) { + return getWeight(operator) - getWeight(other); + } + + static int getWeight(final String operator) { + for (int i = 0; i < ALL.length; i++) { + if (Objects.equals(operator, ALL[i])) { + return i; + } + } + return 0; + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/Rule.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/Rule.java new file mode 100644 index 00000000000..e99d4e0229a --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/Rule.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +public interface Rule { + int getRowNumber(); +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/RuleDefSerializer.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/RuleDefSerializer.java new file mode 100644 index 00000000000..fc8a7311080 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/RuleDefSerializer.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.ArrayList; +import java.util.List; + +import org.treblereel.gwt.yaml.api.YAMLDeserializer; +import org.treblereel.gwt.yaml.api.YAMLSerializer; +import org.treblereel.gwt.yaml.api.exception.YAMLDeserializationException; +import org.treblereel.gwt.yaml.api.internal.deser.YAMLDeserializationContext; +import org.treblereel.gwt.yaml.api.internal.ser.YAMLSerializationContext; +import org.treblereel.gwt.yaml.api.node.YamlMapping; +import org.treblereel.gwt.yaml.api.node.YamlNode; +import org.treblereel.gwt.yaml.api.node.YamlSequence; + +public class RuleDefSerializer + implements YAMLSerializer, YAMLDeserializer { + + private int rowNumber = 1; + + @Override + public Object deserialize(YamlMapping yamlMapping, + String s, + YAMLDeserializationContext yamlDeserializationContext) throws YAMLDeserializationException { + return deserialize(yamlMapping.getNode(s), yamlDeserializationContext); + } + + @Override + public Object deserialize(YamlNode yamlNode, + YAMLDeserializationContext yamlDeserializationContext) { + if (yamlNode instanceof YamlSequence) { + final List items = getItems(yamlNode); + return new InlineRule(rowNumber++, items); + + } else if (yamlNode instanceof YamlMapping) { + final WhenThenRule whenThenRule = new WhenThenRule(rowNumber++); + final YamlNode when = ((YamlMapping) yamlNode).getNode("when"); + final YamlNode then = ((YamlMapping) yamlNode).getNode("then"); + whenThenRule.setWhen(getItems(when)); + whenThenRule.setThen(then.asScalar().value()); + return whenThenRule; + } + return new IllegalArgumentException("Unknown rule format."); + } + + private List getItems(final YamlNode yamlNode) { + final List result = new ArrayList<>(); + if (yamlNode instanceof YamlSequence) { + ((YamlSequence) yamlNode).iterator().forEachRemaining(x -> { + final Comparable value = (Comparable) x.asScalar().value(); + result.add(value); + }); + } + return result; + } + + @Override + public void serialize(YamlMapping yamlMapping, + String s, + Object objects, + YAMLSerializationContext yamlSerializationContext) { + // Not needed, we never serialize. + } + + @Override + public void serialize(YamlSequence yamlSequence, + Object objects, + YAMLSerializationContext yamlSerializationContext) { + // Not needed, we never serialize. + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRule.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRule.java new file mode 100644 index 00000000000..b552f714585 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRule.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.List; + +public class WhenThenRule implements Rule { + + private final int rowNumber; + private List when; + private Object then; + + public WhenThenRule(int rowNumber) { + this.rowNumber = rowNumber; + } + + @Override + public int getRowNumber() { + return rowNumber; + } + + public List getWhen() { + return when; + } + + public Object getThen() { + return then; + } + + public void setWhen(List when) { + this.when = when; + } + + public void setThen(Object then) { + this.then = then; + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRuleThenSerializer.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRuleThenSerializer.java new file mode 100644 index 00000000000..8acb0753893 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/WhenThenRuleThenSerializer.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.Locale; + +import org.treblereel.gwt.yaml.api.YAMLDeserializer; +import org.treblereel.gwt.yaml.api.YAMLSerializer; +import org.treblereel.gwt.yaml.api.exception.YAMLDeserializationException; +import org.treblereel.gwt.yaml.api.internal.deser.YAMLDeserializationContext; +import org.treblereel.gwt.yaml.api.internal.ser.YAMLSerializationContext; +import org.treblereel.gwt.yaml.api.node.YamlMapping; +import org.treblereel.gwt.yaml.api.node.YamlNode; +import org.treblereel.gwt.yaml.api.node.YamlSequence; + +public class WhenThenRuleThenSerializer + implements YAMLSerializer, YAMLDeserializer { + @Override + public Object deserialize(YamlMapping yamlMapping, String key, YAMLDeserializationContext yamlDeserializationContext) throws YAMLDeserializationException { + return deserialize(yamlMapping.getNode(key), yamlDeserializationContext); + } + + @Override + public Object deserialize(YamlNode yamlNode, YAMLDeserializationContext yamlDeserializationContext) { + if (yamlNode == null || yamlNode.isEmpty()) { + return null; + } + return yamlNode. asScalar().value().toLowerCase(Locale.ROOT); + } + + @Override + public void serialize(YamlMapping yamlMapping, String s, Object o, YAMLSerializationContext yamlSerializationContext) { + // Not needed, we never serialize. + } + + @Override + public void serialize(YamlSequence yamlSequence, Object o, YAMLSerializationContext yamlSerializationContext) { + // Not needed, we never serialize. + } +} diff --git a/yard/yard-api/src/main/java/org/kie/yard/api/model/YaRD.java b/yard/yard-api/src/main/java/org/kie/yard/api/model/YaRD.java new file mode 100644 index 00000000000..d35a35fd2c9 --- /dev/null +++ b/yard/yard-api/src/main/java/org/kie/yard/api/model/YaRD.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.api.model; + +import java.util.List; + +import org.treblereel.gwt.yaml.api.annotation.YAMLMapper; + +@YAMLMapper +public class YaRD { + + private String specVersion = "alpha"; + private String kind = "YaRD"; + private String name; + private String expressionLang; + private List inputs; + private List elements; + + public void setInputs(List inputs) { + this.inputs = inputs; + } + + public void setElements(List elements) { + this.elements = elements; + } + + public String getName() { + return name; + } + + public String getExpressionLang() { + return expressionLang; + } + + public void setExpressionLang(String expressionLang) { + this.expressionLang = expressionLang; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getSpecVersion() { + return specVersion; + } + + public void setSpecVersion(String specVersion) { + this.specVersion = specVersion; + } + + public void setName(String name) { + this.name = name; + } + + public List getInputs() { + return inputs; + } + + public List getElements() { + return elements; + } +} diff --git a/yard/yard-api/src/main/resources/YaRD-schema.json b/yard/yard-api/src/main/resources/YaRD-schema.json new file mode 100644 index 00000000000..95d19677960 --- /dev/null +++ b/yard/yard-api/src/main/resources/YaRD-schema.json @@ -0,0 +1,191 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DecisionTable-1": { + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "type": "string" + } + }, + "hitPolicy": { + "type": "string", + "default": "ANY" + }, + "rules": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/InlineRule" + }, + { + "$ref": "#/definitions/WhenThenRule" + } + ] + } + }, + "outputComponents": { + "description": "deprecated", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "inputs", + "rules" + ] + }, + "DecisionTable-2": { + "allOf": [ + { + "$ref": "#/definitions/DecisionTable-1" + }, + { + "type": "object", + "properties": { + "type": { + "const": "DecisionTable" + } + }, + "required": [ + "type" + ] + } + ] + }, + "Element": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "requirements": { + "type": "array", + "items": { + "type": "string" + } + }, + "logic": { + "anyOf": [ + { + "$ref": "#/definitions/DecisionTable-2" + }, + { + "$ref": "#/definitions/LiteralExpression-2" + } + ] + } + }, + "required": [ + "name", + "type", + "logic" + ] + }, + "InlineRule": { + "type": "array", + "items": {} + }, + "Input": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ] + }, + "LiteralExpression-1": { + "type": "object", + "properties": { + "expression": { + "type": "string" + } + }, + "required": [ + "expression" + ] + }, + "LiteralExpression-2": { + "allOf": [ + { + "$ref": "#/definitions/LiteralExpression-1" + }, + { + "type": "object", + "properties": { + "type": { + "const": "LiteralExpression" + } + }, + "required": [ + "type" + ] + } + ] + }, + "WhenThenRule": { + "type": "object", + "properties": { + "when": { + "type": "array", + "items": {} + }, + "then": {} + }, + "required": [ + "when", + "then" + ] + } + }, + "type": "object", + "properties": { + "specVersion": { + "type": "string", + "default": "alpha" + }, + "kind": { + "type": "string", + "default": "YaRD" + }, + "name": { + "type": "string", + "description": "when not provided explicitly, implementation will attempt to deduce the name from the runtime context; if a name cannot be deduced it is an error." + }, + "expressionLang": { + "type": "string", + "description": "An implementation is free to assume a default expressionLang if not explicitly set. For the purpose of a User sharing a YaRD definition, is best to valorise this field explicit." + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/definitions/Input" + } + }, + "elements": { + "type": "array", + "items": { + "$ref": "#/definitions/Element" + } + } + }, + "required": [ + "inputs", + "elements" + ] +} \ No newline at end of file diff --git a/yard/yard-core/pom.xml b/yard/yard-core/pom.xml new file mode 100644 index 00000000000..009d9e89a1f --- /dev/null +++ b/yard/yard-core/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.kie.kogito + yard + 999-SNAPSHOT + + + yard-core + + + org.kie.yard.core + + + + + + org.kie.kogito + kogito-kie-bom + ${project.version} + pom + import + + + + + + + org.kie.kogito + yard-api + + + org.drools + drools-canonical-model + + + org.drools + drools-ruleunits-api + + + org.drools + drools-ruleunits-dsl + + + org.treblereel.gwt.yaml.mapper + common + + + + ch.obermuhlner + jshell-scriptengine + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + \ No newline at end of file diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/DTableUnitBuilder.java b/yard/yard-core/src/main/java/org/kie/yard/core/DTableUnitBuilder.java new file mode 100644 index 00000000000..9a5ded8fbd1 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/DTableUnitBuilder.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.drools.model.Index; +import org.drools.ruleunits.api.SingletonStore; +import org.drools.ruleunits.dsl.SyntheticRuleUnit; +import org.drools.ruleunits.dsl.SyntheticRuleUnitBuilder; +import org.kie.yard.api.model.InlineRule; +import org.kie.yard.api.model.Rule; +import org.kie.yard.api.model.WhenThenRule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +public class DTableUnitBuilder { + private final YaRDDefinitions definitions; + private final String name; + private final OnExecute executionAction; + private final List rules; + private final List inputs; + private JsonMapper jsonMapper = JsonMapper.builder().build(); + + public DTableUnitBuilder(final YaRDDefinitions definitions, + final String name, + final org.kie.yard.api.model.DecisionTable dtableDefinition) { + this.inputs = dtableDefinition.getInputs(); + if (inputs.isEmpty()) { + throw new IllegalStateException("Empty decision table?"); + } + this.definitions = definitions; + this.name = name; + this.rules = dtableDefinition.getRules(); + this.executionAction = getExecutionAction(dtableDefinition.getHitPolicy()); + } + + public SyntheticRuleUnit build() { + + final SyntheticRuleUnitBuilder unit = SyntheticRuleUnitBuilder.build(name); + for (Map.Entry> e : definitions.inputs().entrySet()) { + unit.registerDataSource(e.getKey(), e.getValue(), Object.class); + } + final StoreHandle result = StoreHandle.empty(Object.class); + unit.registerGlobal(name, result); + definitions.outputs().put(name, result); + return unit.defineRules(rulesFactory -> { + for (Rule ruleDefinition : rules) { + var rule = rulesFactory.rule(); + for (int idx = 0; idx < inputs.size(); idx++) { + final RuleCell ruleCell = parseGenericRuleCell(ruleDefinition, idx); + if (ruleCell.value != null) { + final SingletonStore dataSource = definitions.inputs().get(inputs.get(idx)); + + rule.on(dataSource).filter(ruleCell.idxtype, ruleCell.value); + } + } + rule.execute(result, storeHandle -> executionAction.onExecute(ruleDefinition, storeHandle)); + } + }); + } + + private OnExecute getExecutionAction(String hitPolicy) { + if (hitPolicy == null || Objects.equals("ANY", hitPolicy)) { + return (ruleDefinition, storeHandle) -> { + final RuleCell rc = parseGenericRuleThen(ruleDefinition); + storeHandle.set(rc.value); + }; + } else if (Objects.equals("FIRST", hitPolicy)) { + return (ruleDefinition, storeHandle) -> { + if (!storeHandle.isValuePresent()) { + final RuleCell rc = parseGenericRuleThen(ruleDefinition); + storeHandle.set(rc.value); + } + }; + } else if (Objects.equals("COLLECT", hitPolicy)) { + return (ruleDefinition, storeHandle) -> { + if (!storeHandle.isValuePresent()) { + storeHandle.set(new ArrayList<>()); + } + final RuleCell rc = parseGenericRuleThen(ruleDefinition); + + if (storeHandle.get() instanceof List list) { + list.add(resolveValue(rc)); + } + + }; + } else { + throw new UnsupportedOperationException("Not implemented "); + } + } + + private Object resolveValue(final RuleCell rc) { + try { + if (rc.value instanceof String text) { + return jsonMapper.readValue(text, Map.class); + } + } catch (JsonProcessingException ignored) { + } + return rc.value; + } + + private RuleCell parseGenericRuleThen(Rule rule) { + if (rule instanceof InlineRule inlineRule) { + return parseRuleCell(inlineRule.getDef().get(inlineRule.getDef().size() - 1)); + } else if (rule instanceof WhenThenRule whenThenRule) { + return parseRuleCell((whenThenRule).getThen()); + } else { + throw new IllegalStateException("Unknown or unmanaged rule instance type?"); + } + } + + private RuleCell parseGenericRuleCell(Rule rule, int i) { + if (rule instanceof InlineRule inlineRule) { + return parseRuleCell((inlineRule).getDef().get(i)); + } else if (rule instanceof WhenThenRule whenThenRule) { + return parseRuleCell((whenThenRule).getWhen().get(i)); + } else { + throw new IllegalStateException("Unknown or unmanaged rule instance type?"); + } + } + + private RuleCell parseRuleCell(Object object) { + if (object instanceof Boolean) { + return new RuleCell(Index.ConstraintType.EQUAL, object); + } else if (object instanceof Number) { + return new RuleCell(Index.ConstraintType.EQUAL, object); + } else if (object instanceof String valueString) { + if (valueString.startsWith("<=")) { // pay attention to ordering when not using a parser like in this case. + return new RuleCell(Index.ConstraintType.LESS_OR_EQUAL, parseConstrainedCellString(valueString.substring(2))); + } else if (valueString.startsWith(">=")) { + return new RuleCell(Index.ConstraintType.GREATER_OR_EQUAL, parseConstrainedCellString(valueString.substring(2))); + } else if (valueString.startsWith("<")) { + return new RuleCell(Index.ConstraintType.LESS_THAN, parseConstrainedCellString(valueString.substring(1))); + } else if (valueString.startsWith(">")) { + return new RuleCell(Index.ConstraintType.GREATER_THAN, parseConstrainedCellString(valueString.substring(1))); + } else { + return new RuleCell(Index.ConstraintType.EQUAL, parseConstrainedCellString(valueString)); + } + } else { + throw new IllegalStateException("Unmanaged case, please report!"); + } + } + + private Object parseConstrainedCellString(String substring) { + if (Objects.equals("true", substring.trim().toLowerCase())) { + return true; + } + if (Objects.equals("false", substring.trim().toLowerCase())) { + return false; + } + try { + return Integer.parseInt(substring.trim()); + } catch (Exception e) { + + } + try { + return Long.parseLong(substring.trim()); + } catch (Exception e) { + + } + if (Objects.equals("-", substring.trim())) { + return null; + } + return substring; + } + + private interface OnExecute { + void onExecute(Rule ruleDefinition, StoreHandle storeHandle); + } + + public static record RuleCell(Index.ConstraintType idxtype, Object value) { + } + +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/Firable.java b/yard/yard-core/src/main/java/org/kie/yard/core/Firable.java new file mode 100644 index 00000000000..52b4da99400 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/Firable.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +public interface Firable { + + int fire(Map context, YaRDDefinitions units); + +} \ No newline at end of file diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionBuilder.java b/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionBuilder.java new file mode 100644 index 00000000000..508a23b0416 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionBuilder.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import org.kie.yard.api.model.LiteralExpression; + +public class LiteralExpressionBuilder { + private final YaRDDefinitions definitions; + private final String name; + private final LiteralExpression decisionLogic; + + public LiteralExpressionBuilder(YaRDDefinitions definitions, String name, LiteralExpression decisionLogic) { + this.definitions = definitions; + this.name = name; + this.decisionLogic = decisionLogic; + } + + public Firable build() { + String expr = decisionLogic.getExpression(); + definitions.outputs().put(name, StoreHandle.empty(Object.class)); + return new LiteralExpressionInterpreter(name, QuotedExprParsed.from(expr)); + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionInterpreter.java b/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionInterpreter.java new file mode 100644 index 00000000000..6aeab30ae3d --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/LiteralExpressionInterpreter.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.script.*; + +public class LiteralExpressionInterpreter implements Firable { + private final String name; + private final QuotedExprParsed quoted; + private final ScriptEngine engine; + private final CompiledScript compiledScript; + + public LiteralExpressionInterpreter(String nameString, QuotedExprParsed quotedExprParsed) { + this.name = nameString; + this.quoted = quotedExprParsed; + try { + ScriptEngineManager manager = new ScriptEngineManager(); + engine = manager.getEngineByName("jshell"); + Compilable compiler = (Compilable) engine; + compiledScript = compiler.compile(quoted.getRewrittenExpression()); + } catch (Exception e) { + throw new IllegalArgumentException("parse error", e); + } + } + + @Override + public int fire(Map context, YaRDDefinitions units) { + Bindings bindings = engine.createBindings(); + // deliberately escape all symbols; a normal symbol will + // never be in the detected-by-unquoting set, so this + // set can't be used to selectively put in scope + for (Entry inKV : context.entrySet()) { + bindings.put(QuotedExprParsed.escapeIdentifier(inKV.getKey()), inKV.getValue()); + } + for (Entry> outKV : units.outputs().entrySet()) { + if (!outKV.getValue().isValuePresent()) { + continue; + } + bindings.put(QuotedExprParsed.escapeIdentifier(outKV.getKey()), outKV.getValue().get()); + } + try { + var result = compiledScript.eval(bindings); + units.outputs().get(name).set(result); + return 1; + } catch (ScriptException e) { + throw new RuntimeException("interpretation failed at runtime", e); + } + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/QuotedExprParsed.java b/yard/yard-core/src/main/java/org/kie/yard/core/QuotedExprParsed.java new file mode 100644 index 00000000000..56d1da9e669 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/QuotedExprParsed.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.PrimitiveIterator.OfInt; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.SourceVersion; + +public class QuotedExprParsed { + private static final int ESCAPE_CHAR = "`".codePointAt(0); + + private final Set usedSymbols; + private final String rewrittenExpression; + + private QuotedExprParsed(List usedSymbols, String rewrittenExpression) { + this.usedSymbols = usedSymbols.stream().collect(Collectors.toUnmodifiableSet()); + this.rewrittenExpression = rewrittenExpression; + } + + public String getRewrittenExpression() { + return rewrittenExpression; + } + + public Collection getUsedSymbols() { + return usedSymbols; + } + + public static QuotedExprParsed from(String expr) { + StringBuilder rewittenExpr = new StringBuilder(); + StringBuilder quotedBuffer = new StringBuilder(); + List usedSymbols = new ArrayList<>(); + OfInt it = expr.codePoints().iterator(); + State state = State.UNQUOTED; + while (it.hasNext()) { + int c = it.nextInt(); + if (c == ESCAPE_CHAR) { + switch (state) { + case UNQUOTED: + state = State.QUOTED; + break; + case QUOTED: + state = State.UNQUOTED; + var originalSymbol = quotedBuffer.toString(); + usedSymbols.add(originalSymbol); + var escaped = escapeIdentifier(originalSymbol); + rewittenExpr.append(escaped); + quotedBuffer = new StringBuilder(); + break; + default: + throw new IllegalStateException(); + } + } else { + switch (state) { + case UNQUOTED: + rewittenExpr.appendCodePoint(c); + break; + case QUOTED: + quotedBuffer.appendCodePoint(c); + break; + default: + throw new IllegalStateException(); + } + } + } + return new QuotedExprParsed(usedSymbols, rewittenExpr.toString()); + } + + private static enum State { + UNQUOTED, + QUOTED + } + + public static String escapeIdentifier(String partOfIdentifier) { + String id = partOfIdentifier; + if (!Character.isJavaIdentifierStart(id.charAt(0))) { + id = "_" + id; + } + id = id.replaceAll("_", "__"); + if (SourceVersion.isKeyword(id)) { + id = "_" + id; + } + StringBuilder result = new StringBuilder(); + char[] cs = id.toCharArray(); + for (char c : cs) { + if (Character.isJavaIdentifierPart(c)) { + result.append(c); + } else { + result.append("_" + Integer.valueOf(c)); + } + } + return result.toString(); + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/StoreHandle.java b/yard/yard-core/src/main/java/org/kie/yard/core/StoreHandle.java new file mode 100644 index 00000000000..ece69d7b737 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/StoreHandle.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import org.drools.ruleunits.api.DataHandle; +import org.drools.ruleunits.api.DataSource; +import org.drools.ruleunits.api.SingletonStore; + +public class StoreHandle { + private SingletonStore wrapped; + private DataHandle datahandle; + + private StoreHandle(T value) { + wrapped = DataSource.createSingleton(); + datahandle = wrapped.set(value); + } + + private StoreHandle() { + wrapped = DataSource.createSingleton(); + datahandle = null; + } + + public static StoreHandle of(T value) { + return new StoreHandle<>(value); + } + + public static StoreHandle empty(Class type) { + return new StoreHandle<>(); + } + + public DataHandle set(T value) { + datahandle = wrapped.set(value); + return datahandle; + } + + public void clear() { + datahandle = null; + wrapped.clear(); + } + + public boolean isValuePresent() { + return !(datahandle == null); + } + + public T get() { + if (datahandle == null) { + throw new IllegalStateException("was never set"); + } + @SuppressWarnings("unchecked") + T result = (T) datahandle.getObject(); + return result; + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/SyntheticRuleUnitWrapper.java b/yard/yard-core/src/main/java/org/kie/yard/core/SyntheticRuleUnitWrapper.java new file mode 100644 index 00000000000..60f5e39df65 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/SyntheticRuleUnitWrapper.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +import org.drools.ruleunits.api.RuleUnitInstance; +import org.drools.ruleunits.api.RuleUnitProvider; +import org.drools.ruleunits.dsl.SyntheticRuleUnit; + +public class SyntheticRuleUnitWrapper implements Firable { + private final SyntheticRuleUnit wrapped; + + public SyntheticRuleUnitWrapper(SyntheticRuleUnit wrapped) { + this.wrapped = wrapped; + } + + @Override + public int fire(Map context, YaRDDefinitions units) { + RuleUnitInstance unitInstance = RuleUnitProvider.get().createRuleUnitInstance(wrapped); + return unitInstance.fire(); + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/YaRDDefinitions.java b/yard/yard-core/src/main/java/org/kie/yard/core/YaRDDefinitions.java new file mode 100644 index 00000000000..fb06242f3c9 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/YaRDDefinitions.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.drools.ruleunits.api.SingletonStore; + +public record YaRDDefinitions(Map> inputs, List units, + Map> outputs) { + + public Map evaluate(Map context) { + Map results = new LinkedHashMap<>(context); + for (String inputKey : inputs.keySet()) { + if (!context.containsKey(inputKey)) { + throw new IllegalArgumentException("Missing input key in context: " + inputKey); + } + Object inputValue = context.get(inputKey); + inputs.get(inputKey).set(inputValue); + } + context.put("gg", 10); + for (Firable unit : units) { + unit.fire(context, this); + } + for (Entry> outputSets : outputs.entrySet()) { + results.put(outputSets.getKey(), outputSets.getValue().get()); + } + reset(); + return results; + } + + private void reset() { + inputs.forEach((k, v) -> v.clear()); + outputs.forEach((k, v) -> v.clear()); + } +} diff --git a/yard/yard-core/src/main/java/org/kie/yard/core/YaRDParser.java b/yard/yard-core/src/main/java/org/kie/yard/core/YaRDParser.java new file mode 100644 index 00000000000..c20601bc013 --- /dev/null +++ b/yard/yard-core/src/main/java/org/kie/yard/core/YaRDParser.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.drools.ruleunits.api.DataSource; +import org.kie.yard.api.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YaRDParser { + private static final Logger LOG = LoggerFactory.getLogger(YaRDParser.class); + private final YaRDDefinitions definitions = new YaRDDefinitions(new HashMap<>(), new ArrayList<>(), new HashMap<>()); + + public YaRDParser() { + } + + public YaRDDefinitions parse(String yaml) throws Exception { + final YaRD sd = new YaRD_YamlMapperImpl().read(yaml); + if (!sd.getExpressionLang().equals("jshell")) { + throw new IllegalArgumentException("Only `jshell` is supported as an expression language"); + } + appendInputs(sd.getInputs()); + appendUnits(sd.getElements()); + return definitions; + } + + private void appendUnits(List list) { + for (Element hi : list) { + String nameString = hi.getName(); + LOG.debug("parsing {}", nameString); + Firable decisionLogic = createDecisionLogic(nameString, hi.getLogic()); + definitions.units().add(decisionLogic); + } + } + + private Firable createDecisionLogic(String nameString, DecisionLogic decisionLogic) { + if (decisionLogic instanceof org.kie.yard.api.model.DecisionTable decisionTable) { + return new SyntheticRuleUnitWrapper(new DTableUnitBuilder(definitions, nameString, decisionTable).build()); + } else if (decisionLogic instanceof org.kie.yard.api.model.LiteralExpression literalExpression) { + return new LiteralExpressionBuilder(definitions, nameString, literalExpression).build(); + } else { + throw new UnsupportedOperationException("Not implemented."); + } + } + + private void appendInputs(List list) { + for (Input hi : list) { + String nameString = hi.getName(); + @SuppressWarnings("unused") + Class typeRef = processType(hi.getType()); + definitions.inputs().put(nameString, DataSource.createSingleton()); + } + } + + private Class processType(String string) { + switch (string) { + case "string": + case "number": + case "boolean": + default: + return Object.class; + } + } +} diff --git a/yard/yard-core/src/test/java/org/kie/yard/core/DomesticPackagePricesTest.java b/yard/yard-core/src/test/java/org/kie/yard/core/DomesticPackagePricesTest.java new file mode 100644 index 00000000000..5cc3d21f82a --- /dev/null +++ b/yard/yard-core/src/test/java/org/kie/yard/core/DomesticPackagePricesTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DomesticPackagePricesTest + extends TestBase { + private static final String FILE_NAME = "/domestic-package-prices.yml"; + + @Test + public void testMPackage() throws Exception { + final String CTX = """ + { + "Height":10, + "Width":10, + "Length": 10, + "Weight":10 + } + """; + Map outputJSONasMap = evaluate(CTX, FILE_NAME); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Package", "{ \"Size\": \"M\", \"Cost\": 6.90 }"); + } + + @Test + public void testLPackage() throws Exception { + final String CTX = """ + { + "Height":12, + "Width":10, + "Length": 10, + "Weight":10 + } + """; + Map outputJSONasMap = evaluate(CTX, FILE_NAME); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Package", "{ \"Size\": \"L\", \"Cost\": 8.90 }"); + } + +} diff --git a/yard/yard-core/src/test/java/org/kie/yard/core/ExtraCostsTest.java b/yard/yard-core/src/test/java/org/kie/yard/core/ExtraCostsTest.java new file mode 100644 index 00000000000..55a02971ae2 --- /dev/null +++ b/yard/yard-core/src/test/java/org/kie/yard/core/ExtraCostsTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ExtraCostsTest + extends TestBase { + private static final String FILE_NAME = "/extra-costs.yml"; + + @Test + public void testMPackage() throws Exception { + final String CTX = """ + { + "Fragile":true, + "Package Tracking":true, + "Insurance":true, + "Package Type":"M" + } + """; + Map outputJSONasMap = evaluate(CTX, FILE_NAME); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Total cost of premiums", 40); + } + +} diff --git a/yard/yard-core/src/test/java/org/kie/yard/core/InsuranceBasePriceTest.java b/yard/yard-core/src/test/java/org/kie/yard/core/InsuranceBasePriceTest.java new file mode 100644 index 00000000000..22db5bd59c0 --- /dev/null +++ b/yard/yard-core/src/test/java/org/kie/yard/core/InsuranceBasePriceTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class InsuranceBasePriceTest + extends TestBase { + + private static final String FILE_NAME = "/insurance-base-price.yml"; + + @Test + public void testScenario1() throws Exception { + final String CTX = """ + { + "Age": 47, + "Previous incidents?": false + } + """; + Map outputJSONasMap = evaluate(CTX, FILE_NAME); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Base price", 500); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Downpayment", 50.0); + } + + @Test + public void testScenario2() throws Exception { + final String CTX = """ + { + "Age": 19, + "Previous incidents?": true + } + """; + Map outputJSONasMap = evaluate(CTX, FILE_NAME); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Base price", 1000); + assertThat(outputJSONasMap).hasFieldOrPropertyWithValue("Downpayment", 70.0); + } + +} diff --git a/yard/yard-core/src/test/java/org/kie/yard/core/TestBase.java b/yard/yard-core/src/test/java/org/kie/yard/core/TestBase.java new file mode 100644 index 00000000000..3064cba8e6d --- /dev/null +++ b/yard/yard-core/src/test/java/org/kie/yard/core/TestBase.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.yard.core; + +import java.util.Map; + +import org.drools.util.IoUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +public class TestBase { + + private JsonMapper jsonMapper = JsonMapper.builder().build(); + + protected Map evaluate(String jsonInputCxt, String file) throws Exception { + String yamlDecision = new String(IoUtils.readBytesFromInputStream(this.getClass().getResourceAsStream(file), true)); + + YaRDParser parser = new YaRDParser(); + YaRDDefinitions units = parser.parse(yamlDecision); + + Map inputContext = readJSON(jsonInputCxt); + + Map tempOutCtx = units.evaluate(inputContext); + final String OUTPUT_JSON = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(tempOutCtx); + Map outputJSONasMap = readJSON(OUTPUT_JSON); + + return outputJSONasMap; + } + + @SuppressWarnings("unchecked") + private Map readJSON(final String CONTEXT) throws JsonProcessingException, JsonMappingException { + return jsonMapper.readValue(CONTEXT, Map.class); + } + +} diff --git a/yard/yard-core/src/test/resources/domestic-package-prices.yml b/yard/yard-core/src/test/resources/domestic-package-prices.yml new file mode 100644 index 00000000000..8b921b676cd --- /dev/null +++ b/yard/yard-core/src/test/resources/domestic-package-prices.yml @@ -0,0 +1,30 @@ +specVersion: alpha +kind: YaRD +name: "Traffic Violation" +expressionLang: jshell +inputs: + - name: "Length" + type: integer + - name: "Width" + type: number + - name: "Height" + type: number + - name: "Weight" + type: number +elements: + - name: Package + type: Decision + logic: + type: DecisionTable + # First matching result will be picked + hitPolicy: FIRST + inputs: [ "Height", "Width", "Length", "Weight" ] + rules: + - when: [ '<= 3', '<= 25','<= 35', '<= 2' ] + then: '{ "Size": "S", "Cost": 5.90 }' + - when: [ '<= 11','<= 32','<= 42', '<=25' ] + then: '{ "Size": "M", "Cost": 6.90 }' + - when: [ '<= 19', '<= 36', '<= 60', '<= 25' ] + then: '{ "Size": "L", "Cost": 8.90 }' + - when: [ '<= 37', '<= 36', '<= 60', '<= 25' ] + then: '{ "Size": "XL", "Cost": 10.90}' \ No newline at end of file diff --git a/yard/yard-core/src/test/resources/extra-costs.yml b/yard/yard-core/src/test/resources/extra-costs.yml new file mode 100644 index 00000000000..6c16d351a3a --- /dev/null +++ b/yard/yard-core/src/test/resources/extra-costs.yml @@ -0,0 +1,47 @@ +specVersion: alpha +kind: YaRD +name: "Traffic Violation" +expressionLang: jshell +inputs: + - name: Fragile + type: boolean + - name: Package Tracking + type: boolean + - name: Insurance + type: boolean + - name: Package Type + type: string +elements: + - name: Selected premiums + type: Decision + logic: + type: DecisionTable + # Collect all costs + hitPolicy: COLLECT + inputs: [ Package Type, Fragile, Package Tracking, Insurance ] + rules: + # Insurance for all packages, based on size and if the content is fragile + - when: [ 'S', true, "-", true ] + then: '{ "Name": "Fragile insurance cost", "Price": 10}' + - when: [ 'M', true, "-", true ] + then: '{ "Name": "Fragile insurance cost", "Price": 20}' + - when: [ 'L', true, "-", true ] + then: '{ "Name": "Fragile insurance cost", "Price": 30}' + - when: [ 'XL', true, "-", true ] + then: '{ "Name": "Fragile insurance cost", "Price": 40}' + - when: [ "-", false, "-", true ] + then: '{ "Name": "Regular insurance cost", "Price": 5}' + # Tracking cost is same for all sizes + - when: [ "-", "-", true, "-" ] + then: '{ "Name": "Tracking cost", "Price": 5}' + # Fragile package extra care cost + - when: [ "-", true, "-", "-" ] + then: '{ "Name": "Fragile package shipping cost", "Price": 15}' + - name: "Total cost of premiums" + type: Decision + logic: + type: LiteralExpression + expression: | + ((java.util.List>)`Selected premiums`).stream().map(m -> m.get("Price")).mapToInt(Integer::valueOf).sum(); +# Feels filthy compared to FEEL below +# expression: 'sum( for item in Selected premiums return item.Price )' diff --git a/yard/yard-core/src/test/resources/insurance-base-price.yml b/yard/yard-core/src/test/resources/insurance-base-price.yml new file mode 100644 index 00000000000..b6efed66d95 --- /dev/null +++ b/yard/yard-core/src/test/resources/insurance-base-price.yml @@ -0,0 +1,30 @@ +specVersion: alpha +kind: YaRD +name: 'BasePrice' +expressionLang: jshell +inputs: + - name: 'Age' + type: number + - name: 'Previous incidents?' + type: boolean +elements: + - name: 'Base price' + type: Decision + logic: + type: DecisionTable + inputs: [ 'Age', 'Previous incidents?' ] + rules: + - when: [ '<21', false ] + then: 800 + - when: [ '<21', true ] + then: 1000 + - when: [ '>=21', false ] + then: 500 + - when: [ '>=21', true ] + then: 600 + - name: 'Downpayment' + type: Decision + logic: + type: LiteralExpression + expression: | + Math.max(`Base price` * 0.07, 50) diff --git a/yard/yard-core/src/test/resources/logback-test.xml b/yard/yard-core/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..eef0f400eda --- /dev/null +++ b/yard/yard-core/src/test/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + + + %date{HH:mm:ss.SSS} [%thread] %-5level %class{36}.%method:%line - %msg%n + + + + + + + + + + + + diff --git a/yard/yard-core/src/test/resources/scorecards/README-health-card.yml b/yard/yard-core/src/test/resources/scorecards/README-health-card.yml new file mode 100644 index 00000000000..8f79686f2d6 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/README-health-card.yml @@ -0,0 +1,47 @@ +specVersion: alpha +kind: YaRD +name: "Git Repository Completeness" +expressionLang: jshell +inputs: + - name: "README" + type: string + - name: "Web Page Link" + type: string + - name: "Documentation Link" + type: string +elements: + - name: Has README.md file + type: Decision + logic: + type: LiteralExpression + expression: | + `README` != null + - name: README has link to web page + type: Decision + logic: + type: LiteralExpression + expression: | + `README`.contains( `Web Page Link` ) + - name: README has a link to documentation + type: Decision + logic: + type: LiteralExpression + expression: | + `README`.contains( `Documentation Link` ) + - name: README has a title for 'How to contribute' + type: Decision + logic: + type: LiteralExpression + expression: | + `README`.contains( "#How to contribute" ) + - name: Score + type: Decision + logic: + type: DecisionTable + hitPolicy: COLLECT COUNT + inputs: [ "true == {0}" ] + rules: + - [ "Has README.md file" ] + - [ "README has link to web page" ] + - [ "README has a link to documentation" ] + - [ "README has a title for 'How to contribute'" ] diff --git a/yard/yard-core/src/test/resources/scorecards/README.md b/yard/yard-core/src/test/resources/scorecards/README.md new file mode 100644 index 00000000000..0bfe8deb30c --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/README.md @@ -0,0 +1,8 @@ +Not used by any tests at this point. Used as drafts for upcoming features. + +Each YaRD yml file is a scorecard and has to return a value "Score". +Depending on the draft: + +* The Score is either a % between 0 and 100. +* The Score is calculated from previous expressions, each expression has to return true or it is considered a failure + diff --git a/yard/yard-core/src/test/resources/scorecards/branch-responsibilities.yml b/yard/yard-core/src/test/resources/scorecards/branch-responsibilities.yml new file mode 100644 index 00000000000..f0e9ee96f49 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/branch-responsibilities.yml @@ -0,0 +1,38 @@ +specVersion: alpha +kind: YaRD +name: "Branch responsibilities" +expressionLang: jshell +inputs: + - name: "Main POM" + type: 'http://myapi.org/jsonSchema.json#POM' + - name: "Latest Tag POM" + type: 'http://myapi.org/jsonSchema.json#POM' +elements: + - name: Main is set on SNAPSHOT + type: Decision + logic: + type: LiteralExpression + expression: | + `Main POM`.version.endsWith("-SNAPSHOT") + - name: Main version is higher than latest Tag + type: Decision + logic: + type: LiteralExpression + expression: | + `Main POM`.version > `Latest Tag POM`.version + - name: Tag is not on SNAPSHOT + type: Decision + logic: + type: LiteralExpression + expression: | + !`Latest Tag POM`.version.endWith("-SNAPSHOT") + - name: Score + type: Decision + logic: + type: DecisionTable + hitPolicy: COLLECT COUNT + inputs: [ "true == {0}" ] # this is not in the current spec, neither is inserting the values below + rules: + - [ 'Main is set on SNAPSHOT' ] + - [ 'Main version is higher than latest Tag' ] + - [ 'Tag is not on SNAPSHOT' ] \ No newline at end of file diff --git a/yard/yard-core/src/test/resources/scorecards/drafts/branch-blocked-card.yml b/yard/yard-core/src/test/resources/scorecards/drafts/branch-blocked-card.yml new file mode 100644 index 00000000000..0b4a085bf85 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/drafts/branch-blocked-card.yml @@ -0,0 +1,21 @@ +specVersion: alpha +kind: YaRD +name: "Main broken" +expressionLang: jshell +inputs: + - name: "Creator" + type: string + - name: "Created" + type: datetime + - name: "Closed" + type: datetime + - name: "Severity" +# How long was branch broken +# Was the correct protocol followed, what ever that is +elements: + - name: Time Taken to Ack + type: Decision + logic: + type: LiteralExpression + expression: | + `Reported` -`Acked` diff --git a/yard/yard-core/src/test/resources/scorecards/drafts/closed-prs-card.yml b/yard/yard-core/src/test/resources/scorecards/drafts/closed-prs-card.yml new file mode 100644 index 00000000000..578330c2adb --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/drafts/closed-prs-card.yml @@ -0,0 +1,23 @@ +specVersion: alpha +kind: YaRD +name: "Closed PRs" +expressionLang: jshell +inputs: + - name: "Creator" + type: string + - name: "Created" + type: datetime + - name: "Closed" + type: datetime + - name: "Severity" +# Another one for closed PRs? +# Gap check +# Reviewers acked +# Code smells and so on were in check +elements: + - name: Time Taken to Ack + type: Decision + logic: + type: LiteralExpression + expression: | + `Reported` -`Acked` diff --git a/yard/yard-core/src/test/resources/scorecards/drafts/open-prs-card.yml b/yard/yard-core/src/test/resources/scorecards/drafts/open-prs-card.yml new file mode 100644 index 00000000000..b0eed257ea5 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/drafts/open-prs-card.yml @@ -0,0 +1,21 @@ +specVersion: alpha +kind: YaRD +name: "Open PRs" +expressionLang: jshell +inputs: + - name: "Creator" + type: string + - name: "Created" + type: datetime + - name: "Closed" + type: datetime + - name: "Severity" +# Check they have 2 reviewers set, open - close gap is scored based on length +# Tests are ran, code smells minimal +elements: + - name: Time Taken to Ack + type: Decision + logic: + type: LiteralExpression + expression: | + `Reported` -`Acked` diff --git a/yard/yard-core/src/test/resources/scorecards/drafts/weekly-branch-health-card.yml b/yard/yard-core/src/test/resources/scorecards/drafts/weekly-branch-health-card.yml new file mode 100644 index 00000000000..5bee4356941 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/drafts/weekly-branch-health-card.yml @@ -0,0 +1,22 @@ +specVersion: alpha +kind: YaRD +name: "Main broken" +expressionLang: jshell +inputs: + - name: "Creator" + type: string + - name: "Created" + type: datetime + - name: "Closed" + type: datetime + - name: "Severity" +# Times the nightlies failed +# Amount of test failures +# Build failures +elements: + - name: Time Taken to Ack + type: Decision + logic: + type: LiteralExpression + expression: | + `Reported` -`Acked` diff --git a/yard/yard-core/src/test/resources/scorecards/git-repository-health-card.yml b/yard/yard-core/src/test/resources/scorecards/git-repository-health-card.yml new file mode 100644 index 00000000000..af056f36905 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/git-repository-health-card.yml @@ -0,0 +1,56 @@ +specVersion: alpha +kind: YaRD +name: "Git Repository Completeness" +expressionLang: jshell +inputs: + - name: "Repository Data" + type: 'http://myapi.org/jsonSchema.json#GitRepositoryData' +elements: + - name: Has Owners + type: Decision + logic: + type: LiteralExpression + expression: | + `Git Data`.owners.size > 0 + - name: Has Description + type: Decision + logic: + type: LiteralExpression + expression: | + !`Git Data`.description.trim.isEmpty() + - name: Code of Conduct is Set + type: Decision + logic: + type: LiteralExpression + expression: | + `Git Data`.codeOfConduct != null + && !`Git Data`.codeOfConduct.trim.isEmpty() + - name: Security Information is Set + type: Decision + logic: + type: LiteralExpression + expression: | + `Git Data`.securityInformation != null + && !`Git Data`.securityInformation.trim.isEmpty() + - name: Weighted Score + type: Decision + logic: + type: DecisionTable + hitPolicy: COLLECT + inputs: [ "true == {0}" ] + outputComponents: [ "Line Score" ] + rules: + - when: [ 'Has Owners' ] + then: 4 + - when: [ 'Has Description' ] + then: 1 + - when: [ 'Code of Conduct is Set' ] + then: 4 + - when: [ 'Security Information is Set' ] + then: 2 + - name: Score + type: Decision + logic: + type: LiteralExpression + expression: | + `Weighted Score`.stream.mapToInt(Integer::intValue).sum(); \ No newline at end of file diff --git a/yard/yard-core/src/test/resources/scorecards/resource-limits-card.yml b/yard/yard-core/src/test/resources/scorecards/resource-limits-card.yml new file mode 100644 index 00000000000..984f4fcf319 --- /dev/null +++ b/yard/yard-core/src/test/resources/scorecards/resource-limits-card.yml @@ -0,0 +1,40 @@ +specVersion: alpha +kind: YaRD +name: "Resource Limits" +expressionLang: jshell +inputs: + - name: "CPU Count" + type: number + - name: "Memory Use" + type: number + - name: "Run Started" + type: datetime + - name: "Run Ended" + type: datetime +elements: + - name: Run Time + type: Decision + logic: + type: LiteralExpression + # With JShell this does not work, but this can be done with a Java oneliner + expression: | + `Run Started` -`Run Ended` + - name: Fitting profiles + type: Decision + logic: + type: DecisionTable + hitPolicy: COLLECT + inputs: [ "Run Time", "Memory Use", "CPU Count" ] + # This assumes there are set profiles that include the above parameters + # How well we meet them is visualized in the score + rules: + - when: [ '<=30m', '<=20', '<=1' ] # Low profile + then: 4 + - when: [ '<=3h00m', '<=40', '<=1' ] # Medium + then: 3 + - when: [ '<=4h00m', '<=50', '<=1' ] # High + then: 2 + - when: [ '<=2h30m', '<=60', '<=1' ] # Ultra + then: 1 + - when: [ '-', '-', '-' ] # Unacceptable + then: 0 diff --git a/yard/yard-core/src/test/resources/ticket-score-cards.yml b/yard/yard-core/src/test/resources/ticket-score-cards.yml new file mode 100644 index 00000000000..65d4fb75597 --- /dev/null +++ b/yard/yard-core/src/test/resources/ticket-score-cards.yml @@ -0,0 +1,46 @@ +specVersion: alpha +kind: YaRD +name: "Traffic Violation" +expressionLang: jshell +inputs: + - name: "Tickets" + type: List +elements: + - name: Incident was ack'd within 3 minutes + type: Decision + logic: + type: LiteralExpression + expression: | + `Tickets`.stream().allMatch(t -> TimeUnit.MINUTES.convert(t.reported.getTime() - t.acked.getTime(), TimeUnit.MILLISECONDS) <= 3 ); + - name: Incident was resolved within 5 minutes + type: Decision + logic: + type: LiteralExpression + expression: | + `Tickets`.stream().allMatch(t -> TimeUnit.MINUTES.convert(t.reported.getTime() - t.resolved.getTime(), TimeUnit.MILLISECONDS) <= 5 ); + - name: Bronze Level + type: Decision + logic: + type: DecisionTable + hitPolicy: COLLECT COUNT + inputs: [ "Incident was ack'd within 3 minutes", "Incident was resolved within 5 minutes" ] + rules: + - [ true, '-' ] + - [ '-', true ] + - name: Bronze Complete + type: Decision + logic: + type: LiteralExpression + expression: `Bronze Level` == 2 + - name: Level + type: Decision + logic: + type: DecisionTable + hitPolicy: ANY + inputs: [ "Bronze Complete", "Silver Complete", "Gold Complete" ] + outputComponents: [ "Level" ] + # To reach the next level, previous one has to be fully completed + rules: + - [ true, true, true, 'Gold' ] + - [ true, true, false, 'Silver' ] + - [ true, false, false, 'Bronze' ]