Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.kie.api.definition.type.ClassReactive;
import org.kie.api.definition.type.Expires;
import org.kie.api.definition.type.Expires.Policy;
import org.kie.api.definition.type.PropertyChangeSupport;
import org.kie.api.definition.type.PropertyReactive;
import org.kie.api.definition.type.Role;
import org.kie.api.io.Resource;
Expand Down Expand Up @@ -564,6 +565,7 @@ public static TypeDeclaration createTypeDeclarationForBean(Class<?> cls, Annotat
public static void processTypeAnnotations( TypeDeclaration type, Annotated annotated, PropertySpecificOption propertySpecificOption ) {
configureExpirationOffset( type, annotated );
configurePropertyReactivity( type, annotated, propertySpecificOption );
configurePropertyChangeSupport( type, annotated );
}

private static void configureExpirationOffset( TypeDeclaration type, Annotated annotated ) {
Expand All @@ -582,4 +584,11 @@ private static void configurePropertyReactivity( TypeDeclaration type, Annotated
annotated.hasAnnotation( ClassReactive.class ) );
type.setPropertyReactive(propertyReactive);
}

private static void configurePropertyChangeSupport( TypeDeclaration type, Annotated annotated ) {
// only ever switch it on: a DRL declaration may already have made the type dynamic
if ( annotated.hasAnnotation( PropertyChangeSupport.class ) ) {
type.setDynamic( true );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,7 @@ private void deleteStated( RuleImpl rule, TerminalNode terminalNode, InternalFac

final ObjectTypeConf typeConf = getObjectTypeConfigurationRegistry().getObjectTypeConf( object );

if( typeConf.isDynamic() ) {
removePropertyChangeListener( handle, true );
}
removeDynamicPropertyChangeListener( handle, typeConf );

PropagationContext propagationContext = delete( handle, object, typeConf, rule, terminalNode );

Expand Down Expand Up @@ -543,9 +541,23 @@ protected void afterRetract(InternalFactHandle handle, RuleImpl rule, TerminalNo
public void removeFromObjectStore(InternalFactHandle handle) {
this.objectStore.removeHandle( handle );
ObjectTypeConf typeConf = getObjectTypeConfigurationRegistry().getObjectTypeConf( handle.getObject() );
// expiration reaches here without the entry point lock, which guards dynamicFacts
lock();
try {
removeDynamicPropertyChangeListener( handle, typeConf );
} finally {
unlock();
}
deleteFromTMS( handle, handle.getEqualityKey(), typeConf, null );
}

private void removeDynamicPropertyChangeListener( InternalFactHandle handle, ObjectTypeConf typeConf ) {
// a fact is dynamic either because its type is, or because it was inserted with the dynamic flag
if ( typeConf.isDynamic() || ( dynamicFacts != null && dynamicFacts.contains( handle ) ) ) {
removePropertyChangeListener( handle, true );
}
}

public void addPropertyChangeListener(final InternalFactHandle handle, final boolean dynamicFlag ) {
Object object = handle.getObject();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,22 @@

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.definition.type.Expires;
import org.kie.api.definition.type.Role;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.KieSessionConfiguration;
import org.kie.api.runtime.conf.ClockTypeOption;
import org.kie.api.runtime.rule.FactHandle;
import org.kie.api.time.SessionPseudoClock;
import org.kie.internal.utils.KieHelper;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -62,6 +74,32 @@ public void addPropertyChangeListener(PropertyChangeListener listener) {
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}

public int getListenerCount() {
return support.getPropertyChangeListeners().length;
}
}

@org.kie.api.definition.type.PropertyChangeSupport
public static class AnnotatedDynamicFact extends DynamicFact {
}

@org.kie.api.definition.type.PropertyChangeSupport
@Role(Role.Type.EVENT)
@Expires("1s")
public static class ExpiringDynamicFact extends DynamicFact {
}

public static class Trigger {
private final String name;

public Trigger(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

@Test
Expand Down Expand Up @@ -99,4 +137,117 @@ public void testPropertyChanged() {

assertThat(fact.getValue()).isEqualTo("VAL1");
}
}

@Test
public void testDeleteUnregistersFactInsertedAsDynamic() {
// a fact inserted with the dynamic flag must be released on delete, even if its type isn't dynamic
String drl =
"import " + DynamicFact.class.getCanonicalName() + ";\n" +
"import " + Trigger.class.getCanonicalName() + ";\n" +
"global java.util.List facts\n" +
"rule insertDynamic\n" +
" when\n" +
" $t: Trigger()\n" +
" then\n" +
" delete($t);\n" +
" DynamicFact fact = new DynamicFact();\n" +
" fact.setName($t.getName());\n" +
" facts.add(fact);\n" +
" insert(fact, true);\n" +
" end\n" +
" \n" +
" rule deleteDynamic\n" +
" when\n" +
" $fact: DynamicFact()\n" +
" then\n" +
" delete($fact);\n" +
" end";

KieSession ksession = new KieHelper().addContent(drl, ResourceType.DRL).build().newKieSession();
try {
List<DynamicFact> facts = new ArrayList<>();
ksession.setGlobal("facts", facts);

for (int i = 0; i < 10; i++) {
ksession.insert(new Trigger("t" + i));
ksession.fireAllRules();
}

assertThat(ksession.getFactCount()).isZero();
assertThat(facts).hasSize(10).allSatisfy(fact -> assertThat(fact.getListenerCount()).isZero());
} finally {
ksession.dispose();
}
}

@Test
public void testClassLevelAnnotationMakesTypeDynamic() {
// the class annotation must work like declaring the type with @propertyChangeSupport in DRL
String drl =
"import " + AnnotatedDynamicFact.class.getCanonicalName() + ";\n" +
"rule rule1\n" +
" when\n" +
" $fact: AnnotatedDynamicFact(name == \"user1\")\n" +
" then\n" +
" $fact.setName(\"user2\");\n" +
" end\n" +
" \n" +
" rule rule2\n" +
" when\n" +
" $fact: AnnotatedDynamicFact(name == \"user2\")\n" +
" then\n" +
" $fact.setValue($fact.getValue() + \"VAL1\");\n" +
" end";

KieSession ksession = new KieHelper().addContent(drl, ResourceType.DRL).build().newKieSession();
try {
AnnotatedDynamicFact fact = new AnnotatedDynamicFact();
fact.setName("user1");
fact.setValue("");

FactHandle handle = ksession.insert(fact);
assertThat(fact.getListenerCount()).isEqualTo(1);

ksession.fireAllRules(10);

assertThat(fact.getName()).isEqualTo("user2");
assertThat(fact.getValue()).isEqualTo("VAL1");

ksession.delete(handle);
assertThat(fact.getListenerCount()).isZero();
} finally {
ksession.dispose();
}
}

@Test
public void testExpiryUnregistersDynamicFact() {
String drl =
"import " + ExpiringDynamicFact.class.getCanonicalName() + ";\n" +
"rule rule1\n" +
" when\n" +
" ExpiringDynamicFact()\n" +
" then\n" +
" end";

KieBase kbase = new KieHelper().addContent(drl, ResourceType.DRL).build(EventProcessingOption.STREAM);
KieSessionConfiguration ksconf = KieServices.get().newKieSessionConfiguration();
ksconf.setOption(ClockTypeOption.PSEUDO);
KieSession ksession = kbase.newKieSession(ksconf, null);
try {
ExpiringDynamicFact fact = new ExpiringDynamicFact();
ksession.insert(fact);
ksession.fireAllRules();
assertThat(fact.getListenerCount()).isEqualTo(1);

SessionPseudoClock clock = (SessionPseudoClock) ksession.getSessionClock();
clock.advanceTime(2, TimeUnit.SECONDS);
ksession.fireAllRules();

assertThat(ksession.getFactCount()).isZero();
assertThat(fact.getListenerCount()).isZero();
} finally {
ksession.dispose();
}
}
}
Loading