diff --git a/.ci/jenkins/Jenkinsfile.deploy b/.ci/jenkins/Jenkinsfile.deploy index 22bfc61114d..9d95c6b324a 100644 --- a/.ci/jenkins/Jenkinsfile.deploy +++ b/.ci/jenkins/Jenkinsfile.deploy @@ -135,7 +135,9 @@ pipeline { if (isRelease()) { release.gpgImportKeyFromStringWithoutPassword(getReleaseGpgSignKeyCredsId()) - mavenCommand.withProfiles(['apache-release']) + mavenCommand + .withProfiles(['apache-release']) + .withProperty('only.reproducible') mavenRunClosure() } else { mavenRunClosure() diff --git a/drools-drlonyaml-parent/pom.xml b/drools-drlonyaml-parent/pom.xml index dc5f742ac8e..9976e1ecbfe 100644 --- a/drools-drlonyaml-parent/pom.xml +++ b/drools-drlonyaml-parent/pom.xml @@ -30,12 +30,40 @@ drools-drlonyaml-parent Drools :: DRL on YAML pom - - drools-drlonyaml-schemagen - drools-drlonyaml-model - drools-drlonyaml-todrl - drools-drlonyaml-cli - drools-drlonyaml-cli-tests - drools-drlonyaml-integration-tests - + + + + allSubmodules + + + !only.reproducible + + + + drools-drlonyaml-schemagen + drools-drlonyaml-model + drools-drlonyaml-todrl + drools-drlonyaml-cli + drools-drlonyaml-cli-tests + drools-drlonyaml-integration-tests + + + + + onlyReproducible + + + only.reproducible + + + + drools-drlonyaml-schemagen + drools-drlonyaml-model + drools-drlonyaml-todrl + drools-drlonyaml-cli + drools-drlonyaml-cli-tests + + + + diff --git a/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java b/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java index 6f216edd448..dd96c929fa7 100644 --- a/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java +++ b/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java @@ -22,4 +22,12 @@ public interface ConstraintOperator { BiPredicate asPredicate(); + + default boolean hasIndex() { + return false; + } + + default Index.ConstraintType getIndexType() { + throw new UnsupportedOperationException(); + } } diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java index f7537ca464a..34ebf684070 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/DroolsModelBuildContext.java @@ -31,7 +31,16 @@ public interface DroolsModelBuildContext { String APPLICATION_PROPERTIES_FILE_NAME = "application.properties"; String DEFAULT_PACKAGE_NAME = "org.kie.kogito.app"; + /** + * (boolean) enable/disable global rest endpoint generation (default true) + * + * kogito.generate.rest.(engine_name) -> (boolean) enable/disable engine rest endpoint generation (default true) + * + */ String KOGITO_GENERATE_REST = "kogito.generate.rest"; + /** + * (boolean) dependency injection is available and enabled (default true) + */ String KOGITO_GENERATE_DI = "kogito.generate.di"; Optional getApplicationProperty(String property); diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/DependencyInjectionAnnotator.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/DependencyInjectionAnnotator.java index 133e55e4334..1446895fe55 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/DependencyInjectionAnnotator.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/DependencyInjectionAnnotator.java @@ -160,6 +160,18 @@ default > T withInjection(T node) { */ > T withConfigInjection(T node, String configKey, String defaultValue); + /** + * Annotates given node with Transactional annotation + * + * @param node node to be annotated + */ + default > T withTransactional(T node) { + node.addAnnotation(getTransactionalAnnotation()); + return node; + } + + String getTransactionalAnnotation(); + /** * Annotates and enhances method used to produce messages * diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/CDIDependencyInjectionAnnotator.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/CDIDependencyInjectionAnnotator.java index 0631364e819..b4398bfbd7a 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/CDIDependencyInjectionAnnotator.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/CDIDependencyInjectionAnnotator.java @@ -178,6 +178,11 @@ public > T withFactoryMethod(T node) { return node; } + @Override + public String getTransactionalAnnotation() { + return "jakarta.transaction.Transactional"; + } + @Override public > T withTagAnnotation(T node, NodeList attributes) { node.addAnnotation(new NormalAnnotationExpr(new Name("org.eclipse.microprofile.openapi.annotations.tags.Tag"), attributes)); diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/SpringDependencyInjectionAnnotator.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/SpringDependencyInjectionAnnotator.java index d9a5edf7f52..b54a1b517dc 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/SpringDependencyInjectionAnnotator.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/di/impl/SpringDependencyInjectionAnnotator.java @@ -18,6 +18,10 @@ */ package org.drools.codegen.common.di.impl; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.expr.BinaryExpr; import com.github.javaparser.ast.expr.BooleanLiteralExpr; @@ -36,10 +40,6 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType; import org.drools.codegen.common.di.DependencyInjectionAnnotator; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; - public class SpringDependencyInjectionAnnotator implements DependencyInjectionAnnotator { @Override @@ -181,6 +181,11 @@ public > T withFactoryClass(T node) { return node; } + @Override + public String getTransactionalAnnotation() { + return "org.springframework.transaction.annotation.Transactional"; + } + @Override public > T withFactoryMethod(T node) { node.addAnnotation("org.springframework.context.annotation.Bean"); diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/CDIRestAnnotator.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/CDIRestAnnotator.java index 600eff5f833..7ce162e5854 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/CDIRestAnnotator.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/CDIRestAnnotator.java @@ -29,7 +29,7 @@ public class CDIRestAnnotator implements RestAnnotator { @Override public > boolean isRestAnnotated(T node) { - return Stream.of("POST", "GET", "PUT", "DELETE") + return Stream.of("POST", "GET", "PUT", "DELETE", "PATCH") .map(node::getAnnotationByName) .anyMatch(Optional::isPresent); } diff --git a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/SpringRestAnnotator.java b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/SpringRestAnnotator.java index 1258b4d9d5c..7d480c5f321 100644 --- a/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/SpringRestAnnotator.java +++ b/drools-model/drools-codegen-common/src/main/java/org/drools/codegen/common/rest/impl/SpringRestAnnotator.java @@ -29,7 +29,7 @@ public class SpringRestAnnotator implements RestAnnotator { @Override public > boolean isRestAnnotated(T node) { - return Stream.of("PostMapping", "GetMapping", "PutMapping", "DeleteMapping") + return Stream.of("PostMapping", "GetMapping", "PutMapping", "DeleteMapping", "PatchMapping") .map(node::getAnnotationByName) .anyMatch(Optional::isPresent); } diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java new file mode 100644 index 00000000000..d1b5c6f7768 --- /dev/null +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java @@ -0,0 +1,254 @@ +/** + * 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.drools.model.codegen.execmodel; + +import java.util.function.BiPredicate; + +import org.drools.base.common.NetworkNode; +import org.drools.base.prototype.PrototypeObjectType; +import org.drools.base.rule.IndexableConstraint; +import org.drools.core.reteoo.AlphaNode; +import org.drools.core.reteoo.BetaNode; +import org.drools.core.reteoo.EntryPointNode; +import org.drools.core.reteoo.ObjectTypeNode; +import org.drools.kiesession.rulebase.InternalKnowledgeBase; +import org.drools.model.ConstraintOperator; +import org.drools.model.Index; +import org.drools.model.Model; +import org.drools.model.Rule; +import org.drools.model.codegen.execmodel.domain.Result; +import org.drools.model.impl.ModelImpl; +import org.drools.model.prototype.PrototypeVariable; +import org.drools.modelcompiler.KieBaseBuilder; +import org.drools.modelcompiler.constraints.LambdaConstraint; +import org.junit.Test; +import org.kie.api.KieBase; +import org.kie.api.prototype.PrototypeFact; +import org.kie.api.prototype.PrototypeFactInstance; +import org.kie.api.runtime.KieSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.drools.model.DSL.on; +import static org.drools.model.PatternDSL.rule; +import static org.drools.model.prototype.PrototypeDSL.protoPattern; +import static org.drools.model.prototype.PrototypeDSL.variable; +import static org.drools.model.prototype.PrototypeExpression.fixedValue; +import static org.drools.model.prototype.PrototypeExpression.prototypeField; +import static org.kie.api.prototype.PrototypeBuilder.prototype; + +public class CustomConstraintOperatorTest { + + static class CustomConstraintOperator implements ConstraintOperator { + + public int counter = 0; + + @Override + public BiPredicate asPredicate() { + return (t, v) -> { + counter++; + return t.equals(v); + }; + } + + @Override + public boolean hasIndex() { + return true; + } + + @Override + public Index.ConstraintType getIndexType() { + return Index.ConstraintType.EQUAL; + } + + @Override + public String toString() { + return Index.ConstraintType.EQUAL.toString(); + } + } + + @Test + public void alphaIndexIneffective() { + CustomConstraintOperator customConstraintOperator = new CustomConstraintOperator(); + + PrototypeFact testPrototype = prototype("test").asFact(); + PrototypeVariable testV = variable(testPrototype); + + Rule rule1 = rule("alpha1") + .build( + protoPattern(testV) + .expr(prototypeField("fieldA"), customConstraintOperator, fixedValue(1)), + on(testV).execute((drools, x) -> + drools.insert(new Result("Found")) + ) + ); + + Model model = new ModelImpl().addRule(rule1); + KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model); + KieSession ksession = kieBase.newKieSession(); + + PrototypeFactInstance testFact = testPrototype.newInstance(); + testFact.put("fieldA", 1); + + ksession.insert(testFact); + assertThat(ksession.fireAllRules()).isEqualTo(1); + + // Index is created, but actual alpha index hashing works only with more than 3 nodes + Index index = getFirstAlphaNodeIndex((InternalKnowledgeBase) kieBase, testPrototype); + assertThat(index.getIndexType()).isEqualTo(Index.IndexType.ALPHA); + assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL); + + // alpha index hashing is not effective, so the predicated is called + assertThat(customConstraintOperator.counter).isEqualTo(1); + } + + @Test + public void alphaIndexEffective() { + CustomConstraintOperator customConstraintOperator = new CustomConstraintOperator(); + + PrototypeFact testPrototype = prototype("test").asFact(); + PrototypeVariable testV = variable(testPrototype); + + Rule rule1 = rule("alpha1") + .build( + protoPattern(testV) + .expr(prototypeField("fieldA"), customConstraintOperator, fixedValue(1)), + on(testV).execute((drools, x) -> + drools.insert(new Result("Found")) + ) + ); + Rule rule2 = rule("alpha2") + .build( + protoPattern(testV) + .expr(prototypeField("fieldA"), customConstraintOperator, fixedValue(2)), + on(testV).execute((drools, x) -> + drools.insert(new Result("Found")) + ) + ); + Rule rule3 = rule("alpha3") + .build( + protoPattern(testV) + .expr(prototypeField("fieldA"), customConstraintOperator, fixedValue(3)), + on(testV).execute((drools, x) -> + drools.insert(new Result("Found")) + ) + ); + + Model model = new ModelImpl().addRule(rule1).addRule(rule2).addRule(rule3); + KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model); + KieSession ksession = kieBase.newKieSession(); + + PrototypeFactInstance testFact = testPrototype.newInstance(); + testFact.put("fieldA", 1); + + ksession.insert(testFact); + assertThat(ksession.fireAllRules()).isEqualTo(1); + + Index index = getFirstAlphaNodeIndex((InternalKnowledgeBase) kieBase, testPrototype); + assertThat(index.getIndexType()).isEqualTo(Index.IndexType.ALPHA); + assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL); + + // alpha index hashing is effective, so the predicated is not called + assertThat(customConstraintOperator.counter).isZero(); + } + + private static Index getFirstAlphaNodeIndex(InternalKnowledgeBase kieBase, PrototypeFact testPrototype) { + EntryPointNode epn = kieBase.getRete().getEntryPointNodes().values().iterator().next(); + ObjectTypeNode otn = epn.getObjectTypeNodes().get(new PrototypeObjectType(testPrototype)); + AlphaNode alphaNode = (AlphaNode) otn.getObjectSinkPropagator().getSinks()[0]; + IndexableConstraint constraint = (IndexableConstraint) alphaNode.getConstraint(); + return ((LambdaConstraint) constraint).getEvaluator().getIndex(); + } + + @Test + public void betaIndex() { + CustomConstraintOperator customConstraintOperator = new CustomConstraintOperator(); + + Result result = new Result(); + + PrototypeFact personFact = prototype("org.drools.Person").withField("name").withField("age").asFact(); + + PrototypeVariable markV = variable(personFact); + PrototypeVariable ageMateV = variable(personFact); + + Rule rule = rule("beta") + .build( + protoPattern(markV) + .expr("name", Index.ConstraintType.EQUAL, "Mark"), + protoPattern(ageMateV) + .expr("name", Index.ConstraintType.NOT_EQUAL, "Mark") + .expr("age", customConstraintOperator, markV, "age"), + on(ageMateV, markV).execute((p1, p2) -> result.setValue(p1.get("name") + " is the same age as " + p2.get("name"))) + ); + + Model model = new ModelImpl().addRule(rule); + KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model); + + KieSession ksession = kieBase.newKieSession(); + + PrototypeFactInstance mark = personFact.newInstance(); + mark.put("name", "Mark"); + mark.put("age", 37); + + PrototypeFactInstance john = personFact.newInstance(); + john.put("name", "John"); + john.put("age", 39); + + PrototypeFactInstance paul = personFact.newInstance(); + paul.put("name", "Paul"); + paul.put("age", 37); + + ksession.insert(mark); + ksession.insert(john); + ksession.insert(paul); + + ksession.fireAllRules(); + assertThat(result.getValue()).isEqualTo("Paul is the same age as Mark"); + + Index index = getFirstBetaNodeIndex((InternalKnowledgeBase) kieBase, personFact); + assertThat(index.getIndexType()).isEqualTo(Index.IndexType.BETA); + assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL); + + // When beta index is used, the predicate in the custom operator is not actually called + assertThat(customConstraintOperator.counter).isZero(); + } + + private static Index getFirstBetaNodeIndex(InternalKnowledgeBase kieBase, PrototypeFact testPrototype) { + EntryPointNode epn = kieBase.getRete().getEntryPointNodes().values().iterator().next(); + ObjectTypeNode otn = epn.getObjectTypeNodes().get(new PrototypeObjectType(testPrototype)); + NetworkNode[] sinks = otn.getObjectSinkPropagator().getSinks(); + BetaNode betaNode = findBetaNode(sinks); + + IndexableConstraint constraint = (IndexableConstraint) betaNode.getConstraints()[0]; + return ((LambdaConstraint) constraint).getEvaluator().getIndex(); + } + + private static BetaNode findBetaNode(NetworkNode[] sinks) { + for (NetworkNode sink : sinks) { + if (sink instanceof BetaNode) { + return (BetaNode) sink; + } else { + BetaNode betaNode = findBetaNode(sink.getSinks()); + if (betaNode != null) { + return betaNode; + } + } + } + return null; + } +} diff --git a/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java b/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java index 1952375d118..265a4304297 100644 --- a/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java +++ b/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java @@ -127,16 +127,22 @@ public PrototypePatternDef expr(PrototypeExpression left, ConstraintOperator ope reactOnFields.addAll(left.getImpactedFields()); reactOnFields.addAll(right.getImpactedFields()); + // If operator is not Index.ConstraintType, it may contain Index.ConstraintType internally for indexing purposes + ConstraintOperator operatorAsIndexType = operator; + if (operator.hasIndex()) { + operatorAsIndexType = operator.getIndexType(); + } + expr(createExprId(left, operator, right), asPredicate1(leftExtractor, operator, right.asFunction(prototype)), - createAlphaIndex(left, operator, right, prototype, leftExtractor), + createAlphaIndex(left, operatorAsIndexType, right, prototype, leftExtractor), reactOn( reactOnFields.toArray(new String[reactOnFields.size()])) ); return this; } - private static AlphaIndex createAlphaIndex(PrototypeExpression left, ConstraintOperator operator, PrototypeExpression right, Prototype prototype, Function1 leftExtractor) { - if (left.getIndexingKey().isPresent() && right instanceof PrototypeExpression.FixedValue && operator instanceof Index.ConstraintType constraintType) { + private static AlphaIndex createAlphaIndex(PrototypeExpression left, ConstraintOperator operatorAsIndexType, PrototypeExpression right, Prototype prototype, Function1 leftExtractor) { + if (left.getIndexingKey().isPresent() && right instanceof PrototypeExpression.FixedValue && operatorAsIndexType instanceof Index.ConstraintType constraintType) { String fieldName = left.getIndexingKey().get(); Prototype.Field field = prototype.getField(fieldName); Object value = ((PrototypeExpression.FixedValue) right).getValue(); @@ -167,9 +173,15 @@ public PrototypePatternDef expr(PrototypeExpression left, ConstraintOperator ope reactOnFields.addAll(left.getImpactedFields()); reactOnFields.addAll(right.getImpactedFields()); + // If operator is not Index.ConstraintType, it may contain Index.ConstraintType internally for indexing purposes + ConstraintOperator operatorAsIndexType = operator; + if (operator.hasIndex()) { + operatorAsIndexType = operator.getIndexType(); + } + expr(createExprId(left, operator, right), other, asPredicate2(left.asFunction(prototype), operator, right.asFunction(otherPrototype)), - createBetaIndex(left, operator, right, prototype, otherPrototype), + createBetaIndex(left, operatorAsIndexType, right, prototype, otherPrototype), reactOn( reactOnFields.toArray(new String[reactOnFields.size()])) ); return this; @@ -181,8 +193,8 @@ private static String createExprId(PrototypeExpression left, ConstraintOperator return "expr:" + leftId + ":" + operator + ":" + rightId; } - private BetaIndex createBetaIndex(PrototypeExpression left, ConstraintOperator operator, PrototypeExpression right, Prototype prototype, Prototype otherPrototype) { - if (left.getIndexingKey().isPresent() && operator instanceof Index.ConstraintType constraintType && right.getIndexingKey().isPresent()) { + private BetaIndex createBetaIndex(PrototypeExpression left, ConstraintOperator operatorAsIndexType, PrototypeExpression right, Prototype prototype, Prototype otherPrototype) { + if (left.getIndexingKey().isPresent() && operatorAsIndexType instanceof Index.ConstraintType constraintType && right.getIndexingKey().isPresent()) { String fieldName = left.getIndexingKey().get(); Prototype.Field field = prototype.getField(fieldName); Function1 extractor = left.asFunction(prototype);