This is for Jython. Inspired by @rlkoshak’s expire replacement in which he cleverly scans items containing “expire” metadata and attaches a rule to handle the changes in the item, I made a function to do it in a generic way.
Basically I have written a few generic rules that use metadata. My previous approach was to require the items to be a member of a special group in order to invoke the rule like so:
Group MyRule
Switch MySwitch (MyRule) { MyRule=xxxx }
@rule("MyRule")
@when("Member of MyRule received update")
def myrule(event):
# read the MyRule metadata from the item, and do something
Then by scanning the item registry for items with “MyRule” metadata, I don’t have to require items to be a member of a specific group anymore. The item definition can just be:
Switch MySwitch { MyRule=xxxx }
I’m adapting Rich’s code here (still work in progress)
from core.rules import rule
from core.triggers import when
from core.metadata import get_value
from core.log import logging, LOG_PREFIX, log_traceback
from core.jsr223.scope import items
from core.jsr223.scope import scriptExtension
ruleRegistry = scriptExtension.get("ruleRegistry")
rules_items = {}
def load_metadata_rule(metadata, handler, trigger_type='received update', cleaner=None):
"""
Scans the Item Registry for items with the given metadata and adds the
specified trigger type for the Item using the given handler.
"""
log = logging.getLogger("{}.Metadata Rule Loader".format(LOG_PREFIX))
log.debug("Loading metadata rule '{}'".format(metadata))
# Keep track of items configured this pass
new_items = []
# Scan for items with the given metadata
for item_name in items:
metadata_found = get_value(item_name, metadata)
if metadata_found:
new_items.append(item_name)
if metadata not in rules_items:
rules_items[metadata] = {}
rules_items[metadata][item_name] = rules_items.get(metadata, {}).get(item_name)
log.info("Found {} with '{}'".format(item_name, metadata))
# Remove existing rule
if hasattr(handler, "UID"):
ruleRegistry.remove(handler.UID)
delattr(handler, "triggers")
delattr(handler, "UID")
# Generate triggers
for item_name in new_items:
when("Item {} {}".format(item_name, trigger_type))(handler)
# Create the rule
if hasattr(handler, "triggers"):
rule(metadata)(handler)
if hasattr(handler, "UID"):
log.info("Rule {} loaded successfully".format(metadata))
else:
log.error("Failed to create {} rule".format(metadata))
else:
log.info("Rule {} found no configured items".format(metadata))
# Drop items that no longer exist or no longer have the metadata
if metadata in rules_items:
for item_name in rules_items[metadata]:
if item_name not in new_items:
if item_name in items:
log.debug("Removing item '{}' as it no longer has a valid {} config".format(item_name, metadata))
else:
log.debug("Removing item '{}' as it no longer exists".format(item_name))
if cleaner:
cleaner(item_name, metadata)
rules_items[metadata].pop(item_name)
if not rules_items[metadata]:
rules_items.pop(metadata)
So now in my rule file:
def my_rule(event):
# the implementation of my rule
def my_other_rule(event):
#some other rule
@rule("Load MyRule")
@when("System started")
def load_myrule(event):
load_metadata_rule('MyRule', my_rule, 'received update')
load_metadata_rule('MyOtherRule', my_other_rule)
An improvement to this, which @rlkoshak would also like to have I’m sure, is to automatically reload the triggers whenever there’s a change in the item registry. Can it be done?