This is an automated email from the ASF dual-hosted git repository.
tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git
The following commit(s) were added to refs/heads/main by this push:
new 09e0924d89c [incubator-kie#7137] Executable model ignores
@propertyChangeSupport … (#7146)
09e0924d89c is described below
commit 09e0924d89c59500b0330a4a92a1b170da06ddaa
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Wed Sep 30 17:19:16 2026 +0900
[incubator-kie#7137] Executable model ignores @propertyChangeSupport …
(#7146)
* [incubator-kie#7137] Executable model ignores @propertyChangeSupport on
type declarations
* enhance existing test
---
.../execmodel/PropertyChangeSupportTest.java | 182 +++++++++++++++++++++
.../codegen/execmodel/domain/DynamicFact.java | 65 ++++++++
.../modelcompiler/util/TypeDeclarationUtil.java | 6 +-
.../integrationtests/PropertyReactivityTest.java | 4 +
4 files changed, 256 insertions(+), 1 deletion(-)
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyChangeSupportTest.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyChangeSupportTest.java
new file mode 100644
index 00000000000..0d827395252
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyChangeSupportTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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 org.drools.model.codegen.execmodel.domain.DynamicFact;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.api.runtime.KieSession;
+import org.kie.api.runtime.rule.FactHandle;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@code @propertyChangeSupport} on DRL type declarations in the
executable model.
+ *
+ * Related issue: https://github.com/apache/incubator-kie/issues/7137
+ */
+public class PropertyChangeSupportTest extends BaseModelTest {
+
+ private static final String DRL_HEADER =
+ "import " + DynamicFact.class.getCanonicalName() + ";\n" +
+ "declare DynamicFact\n" +
+ " @propertyChangeSupport\n" +
+ "end\n";
+
+ // -----------------------------------------------------------------------
+ // Plain insert: setter in rule1 fires a PropertyChangeEvent that should
+ // cause rule2 to match without an explicit modify.
+ // -----------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyChangeSupportTriggersUpdate(RUN_TYPE runType) {
+ String drl = DRL_HEADER +
+ "rule rule1\n" +
+ " when\n" +
+ " $f : DynamicFact( name == \"user1\" )\n" +
+ " then\n" +
+ " $f.setName(\"user2\");\n" +
+ "end\n" +
+ "rule rule2\n" +
+ " when\n" +
+ " $f : DynamicFact( name == \"user2\" )\n" +
+ " then\n" +
+ " $f.setValue(\"VAL1\");\n" +
+ "end\n";
+
+ KieSession ksession = getKieSession(runType, drl);
+ try {
+ DynamicFact fact = new DynamicFact();
+ fact.setName("user1");
+
+ ksession.insert(fact);
+ ksession.fireAllRules();
+
+ assertThat(fact.getValue())
+ .as("rule2 should have fired after the property change
triggered by rule1")
+ .isEqualTo("VAL1");
+ } finally {
+ ksession.dispose();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ // Listener lifecycle: registered after insert, removed after delete,
+ // and also removed after dispose.
+ // -----------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testListenerLifecycle(RUN_TYPE runType) {
+ String drl = DRL_HEADER +
+ "rule dummy when DynamicFact() then end\n";
+
+ DynamicFact fact = new DynamicFact();
+
+ assertThat(fact.getPropertyChangeListeners())
+ .as("no listener before insert")
+ .isEmpty();
+
+ KieSession ksession = getKieSession(runType, drl);
+ FactHandle fh = ksession.insert(fact);
+
+ assertThat(fact.getPropertyChangeListeners())
+ .as("one listener must be registered after insert")
+ .hasSize(1);
+
+ ksession.delete(fh);
+
+ assertThat(fact.getPropertyChangeListeners())
+ .as("listener must be removed after delete")
+ .isEmpty();
+
+ // Re-insert so dispose has something to clean up.
+ ksession.insert(fact);
+
+ assertThat(fact.getPropertyChangeListeners())
+ .as("one listener must be registered after re-insert")
+ .hasSize(1);
+
+ ksession.dispose();
+
+ assertThat(fact.getPropertyChangeListeners())
+ .as("listener must be removed after dispose")
+ .isEmpty();
+ }
+
+ // -----------------------------------------------------------------------
+ // RHS insert(fact, true) – deprecated but must keep working.
+ // A trigger fact causes a rule whose RHS uses insert(newFact, true);
+ // the new fact should get a listener and trigger the chain rule.
+ // -----------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testDynamicInsertFromRhsStillWorks(RUN_TYPE runType) {
+ // A "trigger" fact causes ruleInsert to insert a DynamicFact via the
+ // deprecated insert(fact, true) RHS call. The DynamicFact should get a
+ // listener, and ruleName + ruleValue should fire on it.
+ String drl =
+ "import " + DynamicFact.class.getCanonicalName() + ";\n" +
+ "import " + Trigger.class.getCanonicalName() + ";\n" +
+ "declare DynamicFact\n" +
+ " @propertyChangeSupport\n" +
+ "end\n" +
+ "rule ruleInsert\n" +
+ " when\n" +
+ " Trigger()\n" +
+ " then\n" +
+ " " + DynamicFact.class.getSimpleName() + " f = new " +
DynamicFact.class.getSimpleName() + "();\n" +
+ " f.setName(\"user1\");\n" +
+ " insert(f, true);\n" +
+ "end\n" +
+ "rule ruleChangeName\n" +
+ " when\n" +
+ " $f : DynamicFact( name == \"user1\" )\n" +
+ " then\n" +
+ " $f.setName(\"user2\");\n" +
+ "end\n" +
+ "rule ruleSetValue\n" +
+ " when\n" +
+ " $f : DynamicFact( name == \"user2\" )\n" +
+ " then\n" +
+ " $f.setValue(\"VAL1\");\n" +
+ "end\n";
+
+ KieSession ksession = getKieSession(runType, drl);
+ try {
+ ksession.insert(new Trigger());
+ ksession.fireAllRules();
+
+ java.util.Collection<DynamicFact> facts =
ksession.getInstancesOf(DynamicFact.class)
+ .stream().collect(java.util.stream.Collectors.toList());
+
+ assertThat(facts).hasSize(1);
+ assertThat(facts.iterator().next().getValue())
+ .as("ruleSetValue should have fired after the property
change triggered by ruleChangeName (dynamic insert)")
+ .isEqualTo("VAL1");
+ } finally {
+ ksession.dispose();
+ }
+ }
+
+ /** Minimal trigger fact used by testDynamicInsertFromRhsStillWorks. */
+ public static class Trigger {}
+}
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/DynamicFact.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/DynamicFact.java
new file mode 100644
index 00000000000..8387740775d
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/DynamicFact.java
@@ -0,0 +1,65 @@
+/*
+ * 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.domain;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * A JavaBean with PropertyChangeSupport, used to test {@code
@propertyChangeSupport}
+ * in DRL type declarations with the executable model.
+ */
+public class DynamicFact {
+
+ private final PropertyChangeSupport support = new
PropertyChangeSupport(this);
+ private String name;
+ private String value;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ String old = this.name;
+ this.name = name;
+ support.firePropertyChange("name", old, name);
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ String old = this.value;
+ this.value = value;
+ support.firePropertyChange("value", old, value);
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ support.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ support.removePropertyChangeListener(listener);
+ }
+
+ public PropertyChangeListener[] getPropertyChangeListeners() {
+ return support.getPropertyChangeListeners();
+ }
+}
diff --git
a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/util/TypeDeclarationUtil.java
b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/util/TypeDeclarationUtil.java
index 16071370faf..be2c5611d87 100644
---
a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/util/TypeDeclarationUtil.java
+++
b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/util/TypeDeclarationUtil.java
@@ -51,7 +51,8 @@ import static
org.drools.base.rule.TypeDeclaration.createTypeDeclarationForBean;
public class TypeDeclarationUtil {
private static final Set<String> KNOWN_ANNOTATIONS = Set.of(
- "role", "duration", "timestamp", "expires", "propertyReactive",
"classReactive"
+ "role", "duration", "timestamp", "expires", "propertyReactive",
"classReactive",
+ "propertyChangeSupport"
);
public static TypeDeclaration createTypeDeclaration(TypeMetaData metaType,
PropertySpecificOption propertySpecificOption, TypeResolver typeResolver) {
@@ -114,6 +115,9 @@ public class TypeDeclarationUtil {
case "classReactive":
typeDeclaration.setPropertyReactive( false );
break;
+ case "propertyChangeSupport":
+ typeDeclaration.setDynamic( true );
+ break;
}
// For non-defined custom annotations, add them as metadata to the
ClassDefinition
diff --git
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/PropertyReactivityTest.java
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/PropertyReactivityTest.java
index 2b621fb6238..d499554148e 100644
---
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/PropertyReactivityTest.java
+++
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/PropertyReactivityTest.java
@@ -1699,6 +1699,10 @@ public class PropertyReactivityTest {
final State state = new State("initial");
session.insert(state);
+
+ // listener must be registered immediately after insert, before
fireAllRules
+ assertThat(state.getPropertyChangeListeners().length).isEqualTo(1);
+
session.fireAllRules();
assertThat(((List) session.getGlobal("list")).size()).isEqualTo(1);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]