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 c0fe5c99861 [incubator-kie#7185] Preserve Java annotations in classic
compiler when DRL declare block is empty (#7186)
c0fe5c99861 is described below
commit c0fe5c9986101f8bb9985dfd2edbbd077dd5dfa5
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Fri Oct 16 18:13:43 2026 +0900
[incubator-kie#7185] Preserve Java annotations in classic compiler when DRL
declare block is empty (#7186)
An empty or partial DRL declare block for an existing Java class was
resetting the class's annotation settings (Role, Expires, Timestamp,
Duration, ClassReactive, PropertyReactive, PropertyChangeSupport) in
the classic compiler, while the executable model retained them.
The fix introduces Annotated.withFallback() to compose a DRL-first,
Java-fallback annotation source, and uses it in processTypeDeclaration()
so that all annotation values are merged before finalizeConfigurator()
runs. wireFieldAccessors() also uses the composite to wire extractors
for @Timestamp and @Duration.
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../main/java/org/drools/base/rule/Annotated.java | 37 ++++
.../java/org/drools/base/rule/TypeDeclaration.java | 8 +
.../builder/impl/TypeDeclarationConfigurator.java | 6 +
.../builder/impl/TypeDeclarationFactory.java | 18 +-
.../execmodel/AlignAnnotationRuntimeTest.java | 219 +++++++++++++++++++++
.../codegen/execmodel/AlignAnnotationTest.java | 199 +++++++++++++++++++
.../codegen/execmodel/domain/AnnotatedEvent.java | 69 +++++++
.../domain/PropertyChangeSupportFact.java | 38 ++--
.../execmodel/domain/PropertyReactiveFact.java | 35 ++--
9 files changed, 592 insertions(+), 37 deletions(-)
diff --git a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
b/drools-base/src/main/java/org/drools/base/rule/Annotated.java
index 5316638f0b2..ed963c03a8a 100644
--- a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
+++ b/drools-base/src/main/java/org/drools/base/rule/Annotated.java
@@ -20,10 +20,47 @@ package org.drools.base.rule;
import java.lang.annotation.Annotation;
+import org.kie.api.definition.type.ClassReactive;
+import org.kie.api.definition.type.PropertyReactive;
+
public interface Annotated {
boolean hasAnnotation( Class<? extends Annotation> annotationClass );
<A extends Annotation> A getTypedAnnotation( Class<A> annotationClass );
+ static Annotated withFallback(Annotated primary, Annotated fallback) {
+ // @PropertyReactive and @ClassReactive are mutually exclusive
declarations of the
+ // same setting. If primary declares either one, suppress fallback for
both so that
+ // a DRL @propertyReactive does not get combined with a Java
@ClassReactive.
+ boolean primaryDeclaresReactivity =
+ primary.hasAnnotation(PropertyReactive.class) ||
primary.hasAnnotation(ClassReactive.class);
+ return new Annotated() {
+ @Override
+ public boolean hasAnnotation(Class<? extends Annotation>
annotationClass) {
+ if (primary.hasAnnotation(annotationClass)) {
+ return true;
+ }
+ if (primaryDeclaresReactivity
+ && (annotationClass == PropertyReactive.class ||
annotationClass == ClassReactive.class)) {
+ return false;
+ }
+ return fallback.hasAnnotation(annotationClass);
+ }
+
+ @Override
+ public <A extends Annotation> A getTypedAnnotation(Class<A>
annotationClass) {
+ A annotation = primary.getTypedAnnotation(annotationClass);
+ if (annotation != null) {
+ return annotation;
+ }
+ if (primaryDeclaresReactivity
+ && (annotationClass == PropertyReactive.class ||
annotationClass == ClassReactive.class)) {
+ return null;
+ }
+ return fallback.getTypedAnnotation(annotationClass);
+ }
+ };
+ }
+
class ClassAdapter implements Annotated {
private final Class<?> cls;
diff --git
a/drools-base/src/main/java/org/drools/base/rule/TypeDeclaration.java
b/drools-base/src/main/java/org/drools/base/rule/TypeDeclaration.java
index a71b1eb294b..d1448c742a9 100644
--- a/drools-base/src/main/java/org/drools/base/rule/TypeDeclaration.java
+++ b/drools-base/src/main/java/org/drools/base/rule/TypeDeclaration.java
@@ -563,11 +563,19 @@ public class TypeDeclaration
}
public static void processTypeAnnotations( TypeDeclaration type, Annotated
annotated, PropertySpecificOption propertySpecificOption ) {
+ configureRole( type, annotated );
configureExpirationOffset( type, annotated );
configurePropertyReactivity( type, annotated, propertySpecificOption );
configurePropertyChangeSupport( type, annotated );
}
+ private static void configureRole( TypeDeclaration type, Annotated
annotated ) {
+ Role role = annotated.getTypedAnnotation(Role.class);
+ if (role != null) {
+ type.setRole(role.value());
+ }
+ }
+
private static void configureExpirationOffset( TypeDeclaration type,
Annotated annotated ) {
Expires expires = annotated.getTypedAnnotation(Expires.class);
if (expires != null) {
diff --git
a/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationConfigurator.java
b/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationConfigurator.java
index 0ea09db7ae4..8b6b13061b1 100644
---
a/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationConfigurator.java
+++
b/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationConfigurator.java
@@ -99,7 +99,13 @@ public class TypeDeclarationConfigurator {
}
}
+ // Annotations were pre-populated from Java and overridden by DRL in
processAnnotations().
+ // The composite Annotated here re-applies them with DRL priority for
@Timestamp/@Duration
+ // extractor wiring, which requires compiler infrastructure
unavailable in earlier steps.
Annotated annotatedType = toAnnotated(typeDescr);
+ if (type.getTypeClass() != null) {
+ annotatedType = Annotated.withFallback(annotatedType, new
Annotated.ClassAdapter(type.getTypeClass()));
+ }
processMvelBasedAccessors(context, results, pkgRegistry,
annotatedType, type );
processTypeAnnotations( type, annotatedType,
context.getBuilderConfiguration().getOption(PropertySpecificOption.KEY));
return true;
diff --git
a/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationFactory.java
b/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationFactory.java
index 87f7bf430d6..72564ce5159 100644
---
a/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationFactory.java
+++
b/drools-compiler/src/main/java/org/drools/compiler/builder/impl/TypeDeclarationFactory.java
@@ -29,6 +29,7 @@ import org.drools.base.base.ClassFieldInspector;
import org.drools.base.base.CoreComponentsBuilder;
import org.drools.base.factmodel.FieldDefinition;
import org.drools.base.factmodel.GeneratedFact;
+import org.drools.base.rule.Annotated;
import org.drools.base.rule.TypeDeclaration;
import org.drools.compiler.compiler.PackageRegistry;
import org.drools.compiler.compiler.TypeDeclarationError;
@@ -40,6 +41,7 @@ import org.kie.api.definition.type.FactField;
import org.kie.api.definition.type.PropertyChangeSupport;
import org.kie.api.definition.type.Role;
import org.kie.api.definition.type.TypeSafe;
+import org.kie.internal.builder.conf.PropertySpecificOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,6 +73,15 @@ public class TypeDeclarationFactory {
type.setTypeClass( existingClass );
type.setNovel( existingClass == null );
type.setNature( existingClass == null ?
TypeDeclaration.Nature.DEFINITION : TypeDeclaration.Nature.DECLARATION );
+
+ // Pre-populate from Java class annotations so that
finalizeConfigurator()
+ // (which runs before wireFieldAccessors) sees the correct role,
expiration,
+ // reactivity, and dynamic settings. DRL annotations applied next
will override.
+ if ( existingClass != null ) {
+ TypeDeclaration.processTypeAnnotations( type,
+ new Annotated.ClassAdapter( existingClass ),
+ context.getBuilderConfiguration().getOption(
PropertySpecificOption.KEY ) );
+ }
}
processTypeAnnotations(typeDescr, type);
@@ -85,6 +96,9 @@ public class TypeDeclarationFactory {
}
}
+ // Processes annotations from the DRL descriptor, overriding values
pre-populated from
+ // Java class annotations in processTypeDeclaration(). Runs before
finalizeConfigurator(),
+ // which needs @Role and @PropertyChangeSupport already set.
public static void processAnnotations( AbstractClassTypeDeclarationDescr
typeDescr, TypeDeclaration type ) {
Role role = getTypedAnnotation(typeDescr, Role.class);
if (role != null) {
@@ -102,7 +116,9 @@ public class TypeDeclarationFactory {
type.setKind(TypeDeclaration.Kind.TRAIT);
}
- type.setDynamic( typeDescr.hasAnnotation(PropertyChangeSupport.class)
);
+ if (typeDescr.hasAnnotation(PropertyChangeSupport.class)) {
+ type.setDynamic(true);
+ }
}
protected void checkRedeclaration( AbstractClassTypeDeclarationDescr
typeDescr, TypeDeclaration type, PackageRegistry pkgRegistry ) {
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationRuntimeTest.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationRuntimeTest.java
new file mode 100644
index 00000000000..19b3fb73813
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationRuntimeTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.concurrent.TimeUnit;
+
+import org.drools.core.ClockType;
+import org.drools.core.common.DefaultEventHandle;
+import org.drools.model.codegen.execmodel.domain.AnnotatedEvent;
+import org.drools.model.codegen.execmodel.domain.PropertyChangeSupportFact;
+import org.drools.model.codegen.execmodel.domain.PropertyReactiveFact;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.api.KieServices;
+import org.kie.api.builder.model.KieModuleModel;
+import org.kie.api.conf.EventProcessingOption;
+import org.kie.api.runtime.KieSession;
+import org.kie.api.runtime.conf.ClockTypeOption;
+import org.kie.api.runtime.rule.FactHandle;
+import org.kie.api.time.SessionPseudoClock;
+import org.kie.internal.builder.conf.PropertySpecificOption;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies that Java annotations preserved through an empty DRL {@code
declare} block
+ * produce the correct runtime behavior in both classic and executable models.
+ *
+ * @see AlignAnnotationTest
+ * @see <a
href="https://github.com/apache/incubator-kie/issues/7185">incubator-kie#7185</a>
+ */
+public class AlignAnnotationRuntimeTest extends BaseModelTest {
+
+ private static KieModuleModel getCepKieModuleModel() {
+ KieModuleModel model = KieServices.get().newKieModuleModel();
+ model.newKieBaseModel("kb")
+ .setDefault(true)
+ .setEventProcessingMode(EventProcessingOption.STREAM)
+ .newKieSessionModel("ks")
+
.setDefault(true).setClockType(ClockTypeOption.get(ClockType.PSEUDO_CLOCK.getId()));
+ return model;
+ }
+
+ // --- @Role(EVENT): fact should be treated as an event ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testRoleEventRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when AnnotatedEvent() then end\n";
+
+ KieSession ksession = getKieSession(runType, getCepKieModuleModel(),
drl);
+ FactHandle handle = ksession.insert(new AnnotatedEvent());
+ assertThat(handle).isInstanceOf(DefaultEventHandle.class);
+ }
+
+ // --- @Expires("10s"): event should expire after 10 seconds ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testExpiresRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when AnnotatedEvent() then end\n";
+
+ KieSession ksession = getKieSession(runType, getCepKieModuleModel(),
drl);
+ SessionPseudoClock clock = ksession.getSessionClock();
+
+ ksession.insert(new AnnotatedEvent());
+ ksession.fireAllRules();
+ assertThat(ksession.getObjects()).hasSize(1);
+
+ clock.advanceTime(11, TimeUnit.SECONDS);
+ ksession.fireAllRules();
+ assertThat(ksession.getObjects()).isEmpty();
+ }
+
+ // --- @Timestamp("start"): temporal operators should use the start field
---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testTimestampRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when\n" +
+ " $a: AnnotatedEvent(length == 0)\n" +
+ " $b: AnnotatedEvent(length == 1, this after[1ms, 1000ms]
$a)\n" +
+ "then end\n";
+
+ KieSession ksession = getKieSession(runType, getCepKieModuleModel(),
drl);
+
+ // Both inserted at the same pseudo clock time; @Timestamp("start")
should
+ // make the engine use the start field for temporal ordering.
+ ksession.insert(new AnnotatedEvent(0, 0));
+ ksession.insert(new AnnotatedEvent(500, 1));
+ assertThat(ksession.fireAllRules()).isEqualTo(1);
+ }
+
+ // --- @Duration("length"): event duration should span [start,
start+length] ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testDurationRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when\n" +
+ " $a: AnnotatedEvent(length > 0)\n" +
+ " $b: AnnotatedEvent(length == 0, this during $a)\n" +
+ "then end\n";
+
+ KieSession ksession = getKieSession(runType, getCepKieModuleModel(),
drl);
+
+ // $a spans [0, 2000ms], $b at 500ms is during $a
+ ksession.insert(new AnnotatedEvent(0, 2000));
+ ksession.insert(new AnnotatedEvent(500, 0));
+ assertThat(ksession.fireAllRules()).isEqualTo(1);
+ }
+
+ // --- @ClassReactive: modifying any property should re-trigger evaluation
---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testClassReactiveRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when\n" +
+ " $e: AnnotatedEvent(value == null)\n" +
+ "then\n" +
+ // Modify a property NOT in the condition. With
@ClassReactive, this
+ // re-triggers evaluation. With property-reactive, it would
not.
+ " modify($e) { setStart($e.getStart() + 1) }\n" +
+ "end\n";
+
+ KieModuleModel model = KieServices.get().newKieModuleModel();
+ model.setConfigurationProperty(PropertySpecificOption.PROPERTY_NAME,
PropertySpecificOption.ALWAYS.name());
+
+ KieSession ksession = getKieSession(runType, model, drl);
+ ksession.insert(new AnnotatedEvent());
+ int fired = ksession.fireAllRules(10);
+ assertThat(fired).isEqualTo(10);
+ }
+
+ // --- @PropertyReactive: only used-property changes should re-trigger ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyReactiveRuntimeWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + PropertyReactiveFact.class.getCanonicalName() +
";\n" +
+ "declare PropertyReactiveFact end\n" +
+ "rule R when\n" +
+ " $f: PropertyReactiveFact(name == \"test\")\n" +
+ "then\n" +
+ // Modify a property NOT in the condition. With
@PropertyReactive,
+ // this should NOT re-trigger the rule.
+ " modify($f) { setValue($f.getValue() + 1) }\n" +
+ "end\n";
+
+ KieModuleModel model = KieServices.get().newKieModuleModel();
+ model.setConfigurationProperty(PropertySpecificOption.PROPERTY_NAME,
PropertySpecificOption.ALLOWED.name());
+
+ KieSession ksession = getKieSession(runType, model, drl);
+ PropertyReactiveFact fact = new PropertyReactiveFact();
+ fact.setName("test");
+ ksession.insert(fact);
+ int fired = ksession.fireAllRules(10);
+ assertThat(fired).isEqualTo(1);
+ }
+
+ // --- @PropertyChangeSupport: setter calls should trigger re-evaluation
without modify ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyChangeSupportRuntimeWithEmptyDeclare(RUN_TYPE
runType) {
+ String drl =
+ "import " + PropertyChangeSupportFact.class.getCanonicalName()
+ ";\n" +
+ "declare PropertyChangeSupportFact end\n" +
+ "rule R1 when\n" +
+ " $f: PropertyChangeSupportFact(name == \"user1\")\n" +
+ "then\n" +
+ " $f.setName(\"user2\");\n" +
+ "end\n" +
+ "rule R2 when\n" +
+ " PropertyChangeSupportFact(name == \"user2\")\n" +
+ "then\n" +
+ "end\n";
+
+ KieSession ksession = getKieSession(runType, drl);
+ PropertyChangeSupportFact fact = new PropertyChangeSupportFact();
+ fact.setName("user1");
+ ksession.insert(fact);
+ // R1 fires and calls setName("user2") directly (no modify block).
+ // @PropertyChangeSupport should notify the engine, causing R2 to fire.
+ int fired = ksession.fireAllRules(10);
+ assertThat(fired).isEqualTo(2);
+ }
+}
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationTest.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationTest.java
new file mode 100644
index 00000000000..c6cdc938de7
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/AlignAnnotationTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.base.rule.TypeDeclaration;
+import org.drools.core.impl.InternalRuleBase;
+import org.drools.model.codegen.execmodel.domain.AnnotatedEvent;
+import org.drools.model.codegen.execmodel.domain.PropertyChangeSupportFact;
+import org.drools.model.codegen.execmodel.domain.PropertyReactiveFact;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.api.KieServices;
+import org.kie.api.builder.model.KieModuleModel;
+import org.kie.api.definition.type.Role;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.builder.conf.PropertySpecificOption;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Reproduces the annotation-precedence discrepancy between classic and
executable
+ * models when an empty DRL {@code declare} block exists for a Java class.
+ *
+ * <p>{@code @TypeSafe} is not covered here: neither model reads it from Java
annotations
+ * (only from DRL {@code @typesafe}), so there is no inheritance discrepancy
to test.
+ *
+ * @see <a
href="https://github.com/apache/incubator-kie/issues/7185">incubator-kie#7185</a>
+ */
+public class AlignAnnotationTest extends BaseModelTest {
+
+ private static final String ANNOTATED_EVENT_NO_DECLARE_DRL =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "rule R when AnnotatedEvent() then end\n";
+
+ private static final String ANNOTATED_EVENT_EMPTY_DECLARE_DRL =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent end\n" +
+ "rule R when AnnotatedEvent() then end\n";
+
+ private static final String ANNOTATED_EVENT_OVERRIDE_DRL =
+ "import " + AnnotatedEvent.class.getCanonicalName() + ";\n" +
+ "declare AnnotatedEvent\n" +
+ " @role(FACT)\n" +
+ " @expires(5s)\n" +
+ " @propertyReactive\n" +
+ "end\n" +
+ "rule R when AnnotatedEvent() then end\n";
+
+ private TypeDeclaration getTypeDeclaration(RUN_TYPE runType, String drl,
Class<?> clazz) {
+ return getTypeDeclaration(runType, null, drl, clazz);
+ }
+
+ private TypeDeclaration getTypeDeclaration(RUN_TYPE runType,
KieModuleModel model, String drl, Class<?> clazz) {
+ KieSession ksession = getKieSession(runType, model, drl);
+ return ((InternalRuleBase)
ksession.getKieBase()).getTypeDeclaration(clazz);
+ }
+
+ // --- Baseline: no declare block — both models should honor Java
annotations ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testRoleWithNoDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_NO_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getRole()).isEqualTo(Role.Type.EVENT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testExpiresWithNoDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_NO_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getExpirationOffset()).isEqualTo(10_000L);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testTimestampWithNoDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_NO_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getTimestampAttribute()).isEqualTo("start");
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testDurationWithNoDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_NO_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getDurationAttribute()).isEqualTo("length");
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testClassReactiveWithNoDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_NO_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.isPropertyReactive()).isFalse();
+ }
+
+ // --- Empty declare block — should preserve Java annotations ---
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testRoleWithEmptyDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_EMPTY_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getRole()).isEqualTo(Role.Type.EVENT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testExpiresWithEmptyDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_EMPTY_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getExpirationOffset()).isEqualTo(10_000L);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testTimestampWithEmptyDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_EMPTY_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getTimestampAttribute()).isEqualTo("start");
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testDurationWithEmptyDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_EMPTY_DECLARE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getDurationAttribute()).isEqualTo("length");
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testClassReactiveWithEmptyDeclare(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_EMPTY_DECLARE_DRL, AnnotatedEvent.class);
+ // @ClassReactive with PropertySpecificOption.ALWAYS (default) means
NOT property reactive
+ assertThat(typeDecl.isPropertyReactive()).isFalse();
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyReactiveWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + PropertyReactiveFact.class.getCanonicalName() +
";\n" +
+ "declare PropertyReactiveFact end\n" +
+ "rule R when PropertyReactiveFact() then end\n";
+
+ KieModuleModel model = KieServices.get().newKieModuleModel();
+ model.setConfigurationProperty(PropertySpecificOption.PROPERTY_NAME,
PropertySpecificOption.ALLOWED.name());
+
+ TypeDeclaration typeDecl = getTypeDeclaration(runType, model, drl,
PropertyReactiveFact.class);
+ assertThat(typeDecl.isPropertyReactive()).isTrue();
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyChangeSupportWithEmptyDeclare(RUN_TYPE runType) {
+ String drl =
+ "import " + PropertyChangeSupportFact.class.getCanonicalName()
+ ";\n" +
+ "declare PropertyChangeSupportFact end\n" +
+ "rule R when PropertyChangeSupportFact() then end\n";
+
+ TypeDeclaration typeDecl = getTypeDeclaration(runType, drl,
PropertyChangeSupportFact.class);
+ assertThat(typeDecl.isDynamic()).isTrue();
+ }
+
+ // --- Explicit DRL override — DRL annotation should take precedence over
Java ---
+
+ @ParameterizedTest
+ @MethodSource("parametersStandardOnly")
+ public void testRoleExplicitOverride(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_OVERRIDE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getRole()).isEqualTo(Role.Type.FACT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testExpiresExplicitOverride(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_OVERRIDE_DRL, AnnotatedEvent.class);
+ assertThat(typeDecl.getExpirationOffset()).isEqualTo(5_000L);
+ }
+
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testPropertyReactiveExplicitOverride(RUN_TYPE runType) {
+ TypeDeclaration typeDecl = getTypeDeclaration(runType,
ANNOTATED_EVENT_OVERRIDE_DRL, AnnotatedEvent.class);
+ // DRL @propertyReactive overrides Java @ClassReactive
+ assertThat(typeDecl.isPropertyReactive()).isTrue();
+ }
+}
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/AnnotatedEvent.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/AnnotatedEvent.java
new file mode 100644
index 00000000000..078af5dde5b
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/AnnotatedEvent.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.drools.model.codegen.execmodel.domain;
+
+import org.kie.api.definition.type.ClassReactive;
+import org.kie.api.definition.type.Duration;
+import org.kie.api.definition.type.Expires;
+import org.kie.api.definition.type.Role;
+import org.kie.api.definition.type.Timestamp;
+
+@Role(Role.Type.EVENT)
+@Expires("10s")
+@Timestamp("start")
+@Duration("length")
+@ClassReactive
+public class AnnotatedEvent {
+
+ private long start;
+ private long length;
+ private String value;
+
+ public AnnotatedEvent() {
+ }
+
+ public AnnotatedEvent(long start, long length) {
+ this.start = start;
+ this.length = length;
+ }
+
+ public long getStart() {
+ return start;
+ }
+
+ public void setStart(long start) {
+ this.start = start;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ public void setLength(long length) {
+ this.length = length;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyChangeSupportFact.java
similarity index 51%
copy from drools-base/src/main/java/org/drools/base/rule/Annotated.java
copy to
drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyChangeSupportFact.java
index 5316638f0b2..97fd7792baf 100644
--- a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyChangeSupportFact.java
@@ -16,30 +16,32 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.drools.base.rule;
+package org.drools.model.codegen.execmodel.domain;
-import java.lang.annotation.Annotation;
+import java.beans.PropertyChangeListener;
-public interface Annotated {
- boolean hasAnnotation( Class<? extends Annotation> annotationClass );
- <A extends Annotation> A getTypedAnnotation( Class<A> annotationClass );
[email protected]
+public class PropertyChangeSupportFact {
- class ClassAdapter implements Annotated {
+ private final java.beans.PropertyChangeSupport pcs = new
java.beans.PropertyChangeSupport(this);
- private final Class<?> cls;
+ private String name;
- public ClassAdapter( Class<?> cls ) {
- this.cls = cls;
- }
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ String old = this.name;
+ this.name = name;
+ pcs.firePropertyChange("name", old, name);
+ }
- @Override
- public boolean hasAnnotation( Class<? extends Annotation>
annotationClass ) {
- return cls.isAnnotationPresent(annotationClass);
- }
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
- @Override
- public <A extends Annotation> A getTypedAnnotation( Class<A>
annotationClass ) {
- return cls.getAnnotation(annotationClass);
- }
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(listener);
}
}
diff --git a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyReactiveFact.java
similarity index 51%
copy from drools-base/src/main/java/org/drools/base/rule/Annotated.java
copy to
drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyReactiveFact.java
index 5316638f0b2..a9a6f8a23a5 100644
--- a/drools-base/src/main/java/org/drools/base/rule/Annotated.java
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PropertyReactiveFact.java
@@ -16,30 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.drools.base.rule;
+package org.drools.model.codegen.execmodel.domain;
-import java.lang.annotation.Annotation;
+import org.kie.api.definition.type.PropertyReactive;
-public interface Annotated {
- boolean hasAnnotation( Class<? extends Annotation> annotationClass );
- <A extends Annotation> A getTypedAnnotation( Class<A> annotationClass );
+@PropertyReactive
+public class PropertyReactiveFact {
- class ClassAdapter implements Annotated {
+ private String name;
+ private int value;
- private final Class<?> cls;
+ public String getName() {
+ return name;
+ }
- public ClassAdapter( Class<?> cls ) {
- this.cls = cls;
- }
+ public void setName(String name) {
+ this.name = name;
+ }
- @Override
- public boolean hasAnnotation( Class<? extends Annotation>
annotationClass ) {
- return cls.isAnnotationPresent(annotationClass);
- }
+ public int getValue() {
+ return value;
+ }
- @Override
- public <A extends Annotation> A getTypedAnnotation( Class<A>
annotationClass ) {
- return cls.getAnnotation(annotationClass);
- }
+ public void setValue(int value) {
+ this.value = value;
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]