I've found myself in need of something similar to what Wolfgang described
in an archived post in order to handle retractions based on dynamic
properties found on the fact itself:
*****
Just in case anybody wonders about the effort for building a mechanism for
retracting facts based on fact attributes or other data, I outline the
solution I have implemented.
My class FactWiper implements WorkingMemoryEventListener and Runnable.
- The implementation of objectInserted( ObjectInsertedEvent event )
determines whether the object needs handling and, if so, the retraction
Date, based upon whatever strategy you care to implement, e.g., reflection
method call, Map<Class,Long>, etc. If it is to be handled, it signals
Condition "change" to the thread.
- A SortedMap<Date,Object> expiry2fact keeps track of pending
retractions.
- The thread loops into an awaitUntil( x ) on the "change" condition,
where x is (generally) the first retraction Date. If reached, it retracts
according to the foremost Map entry; if signalled it reasesses the
situation and reenters the awaitUntil.
-W
*****
However, I've been thus far unable to beat an IllegalMonitorStateException
I encounter when looping back in from an insertLogical. Below is my
Listener class and my example rules that demonstrate the exception. Anyone
see what I'm doing wrong with the thread conditions? Thanks for your time.
- Jeremy
--------------------- LISTENER CLASS ------------------------
@Slf4j
@Component
class RetractionListener implements WorkingMemoryEventListener, Runnable {
/** allow a sorted map to track dates and objects to be retracted */
NavigableMap<Date, Object> retractionSchedule = new TreeMap<Date,
Object>();
/** allow some condition to signify when we have a new retractable fact
to consider */
Condition change
/** session wrapper */
@Autowired
SessionManager sessionManager
/**
* runnable task entry point for executor
*/
void run() {
Lock lock = new ReentrantLock()
change = lock.newCondition()
lock.lock()
while (retractionSchedule.isEmpty()) {
change.await()
}
try {
while (System.currentTimeMillis() <
retractionSchedule.firstKey().time) {
log.debug("issuing wait")
change.awaitUntil(retractionSchedule.firstKey())
}
FactHandle handle =
sessionManager.factHandleMap.get(retractionSchedule.firstEntry().value)
sessionManager.session.retract(handle)
} catch (InterruptedException e) {
log.error("RetractionListener thread
${Thread.currentThread().name} interrupted!")
throw e
} finally {
lock.unlock()
}
}
/**
* detect new facts, determine if they are retractable, and add to the
tracker map if needed
*
* @param event
*/
void objectInserted(ObjectInsertedEvent event) {
if (event.object.isRetractable) {
log.debug("retractable object of type
${event.object.class.simpleName} detected")
try {
long duration = event.object.duration
if (!duration) {
// go ahead and throw up a similar exception to missing
property for a missing value
throw new MissingPropertyException("no value specified
for retractable object's duration")
} else {
Calendar calendar = GregorianCalendar.getInstance()
log.debug("duration of object noted to be ${duration}
milliseconds")
log.debug("current time: ${calendar.time}")
calendar.add(Calendar.MILLISECOND, duration.toInteger())
log.debug("setting schedule for ${calendar.time}")
retractionSchedule.put(calendar.time, event.object)
log.debug("signaling change condition")
change.signal()
}
} catch (MissingPropertyException e) {
log.error("retractable object ${event.object} missing
needed property/value 'duration'")
throw e
}
}
}
...[REST OF CLASS IRRELEVANT]...
}
--------------------- RULE FILE -------------------------
declare Fire
@role(event)
end
declare SprinklerInterval
@role(event)
fire : Fire
isRetractable : boolean
duration : long
end
rule "fire detected"
when
$f : Fire ( )
not SprinklerInterval ( fire == $f )
then
insertLogical ( new SprinklerInterval($f, true, 500) );
end
rule "add sprinklers running notification"
when
$s : SprinklerInterval ( )
then
log.info("Sprinklers running on fire located in: " +
$s.getFire().getName());
end