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.
-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