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]

Reply via email to