/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.core.drools.internal;
import static org.openhab.core.events.EventConstants.TOPIC_PREFIX;
import static org.openhab.core.events.EventConstants.TOPIC_SEPERATOR;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.ObjectFilter;
import org.drools.SystemEventListener;
import org.drools.SystemEventListenerFactory;
import org.drools.agent.KnowledgeAgent;
import org.drools.agent.KnowledgeAgentConfiguration;
import org.drools.agent.KnowledgeAgentFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceChangeScannerConfiguration;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.rule.FactHandle;
import org.openhab.core.drools.event.CommandEvent;
import org.openhab.core.drools.event.RuleEvent;
import org.openhab.core.drools.event.StateEvent;
import org.openhab.core.items.GenericItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.ItemRegistryChangeListener;
import org.openhab.core.items.StateChangeListener;
import org.openhab.core.service.AbstractActiveService;
import org.openhab.core.types.Command;
import org.openhab.core.types.EventType;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RuleService extends AbstractActiveService implements ManagedService, EventHandler, ItemRegistryChangeListener, StateChangeListener {
private static final String RULES_CHANGESET = "org/openhab/core/drools/changeset.xml";
static private final Logger logger = LoggerFactory.getLogger(RuleService.class);
private ItemRegistry itemRegistry = null;
private long refreshInterval = 200;
private StatefulKnowledgeSession ksession = null;
private Map<String, FactHandle> factHandleMap = new HashMap<String, FactHandle>();
private List<RuleEvent> eventQueue = Collections.synchronizedList(new ArrayList<RuleEvent>());
public void activate() {
SystemEventListenerFactory.setSystemEventListener(new RuleEventListener());
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource(RULES_CHANGESET, getClass()), ResourceType.CHANGE_SET);
if(kbuilder.hasErrors()) {
logger.error("There are errors in the rules: " + kbuilder.getErrors());
return;
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
KnowledgeAgentConfiguration aconf = KnowledgeAgentFactory.newKnowledgeAgentConfiguration();
aconf.setProperty("drools.agent.newInstance", "false");
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent("RuleAgent", kbase, aconf);
kagent.applyChangeSet(ResourceFactory.newClassPathResource(RULES_CHANGESET, getClass()));
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
ksession = kbase.newStatefulKnowledgeSession();
// activate notifications
ResourceFactory.getResourceChangeNotifierService().start();
ResourceFactory.getResourceChangeScannerService().start();
// activate this for extensive logging
// KnowledgeRuntimeLoggerFactory.newConsoleLogger(ksession);
// set the scan interval to 20 secs
ResourceChangeScannerConfiguration sconf = ResourceFactory.getResourceChangeScannerService().newResourceChangeScannerConfiguration();
sconf.setProperty( "drools.resource.scanner.interval", "20" );
ResourceFactory.getResourceChangeScannerService().configure(sconf);
// now add all registered items to the session
if(itemRegistry!=null) {
for(Item item : itemRegistry.getItems()) {
itemAdded(item);
}
}
setProperlyConfigured(true);
}
public void deactivate() {
if(ksession!=null) {
ksession.dispose();
ksession = null;
}
factHandleMap.clear();
shutdown();
}
public void setItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = itemRegistry;
itemRegistry.addItemRegistryChangeListener(this);
}
public void unsetItemRegistry(ItemRegistry itemRegistry) {
itemRegistry.removeItemRegistryChangeListener(this);
this.itemRegistry = null;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
public void updated(Dictionary config) throws ConfigurationException {
if (config != null) {
String evalIntervalString = (String) config.get("evalInterval");
if (StringUtils.isNotBlank(evalIntervalString)) {
refreshInterval = Long.parseLong(evalIntervalString);
}
}
}
/**
* {@inheritDoc}
*/
public void allItemsChanged(Collection<String> oldItemNames) {
if(ksession!=null) {
// first remove all previous items from the session
for(String oldItemName : oldItemNames) {
internalItemRemoved(oldItemName);
}
// then add the current ones again
Collection<Item> items = itemRegistry.getItems();
for(Item item : items) {
internalItemAdded(item);
}
}
}
/**
* {@inheritDoc}
*/
public void itemAdded(Item item) {
if(ksession!=null) {
internalItemAdded(item);
}
}
/**
* {@inheritDoc}
*/
public void itemRemoved(Item item) {
if(ksession!=null) {
internalItemRemoved(item.getName());
if (item instanceof GenericItem) {
GenericItem genericItem = (GenericItem) item;
genericItem.removeStateChangeListener(this);
}
}
}
/**
* {@inheritDoc}
*/
public void stateChanged(Item item, State oldState, State newState) {
eventQueue.add(new StateEvent(item, oldState, newState));
}
/**
* {@inheritDoc}
*/
public void stateUpdated(Item item, State state) {
eventQueue.add(new StateEvent(item, state));
}
public void receiveCommand(String itemName, Command command) {
try {
Item item = itemRegistry.getItem(itemName);
eventQueue.add(new CommandEvent(item, command));
} catch (ItemNotFoundException e) {}
}
private void internalItemAdded(Item item) {
if(item==null) {
logger.debug("Item must not be null here!");
return;
}
FactHandle handle = factHandleMap.get(item.getName());
if(handle!=null) {
// we already know this item
try {
ksession.update(handle, item);
} catch(NullPointerException e) {
// this can be thrown because of a bug in drools when closing down the system
}
} else {
// it is a new item
handle = ksession.insert(item);
factHandleMap.put(item.getName(), handle);
if (item instanceof GenericItem) {
GenericItem genericItem = (GenericItem) item;
genericItem.addStateChangeListener(this);
}
}
}
private void internalItemRemoved(String itemName) {
FactHandle handle = factHandleMap.get(itemName);
if(handle!=null) {
factHandleMap.remove(itemName);
ksession.retract(handle);
}
}
/**
* @{inheritDoc}
*/
@Override
protected synchronized void execute() {
// remove all previous events from the session
Collection<FactHandle> handles = ksession.getFactHandles(new ObjectFilter() {
public boolean accept(Object obj) {
if (obj instanceof RuleEvent) {
return true;
}
return false;
}
});
for(FactHandle handle : handles) {
ksession.retract(handle);
}
ArrayList<RuleEvent> clonedQueue = new ArrayList<RuleEvent>(eventQueue);
eventQueue.clear();
// now add all recent events to the session
for(RuleEvent event : clonedQueue) {
Item item = event.getItem();
if(ksession!=null && item!=null) {
FactHandle factHandle = factHandleMap.get(item.getName());
if(factHandle!=null) {
ksession.update(factHandle, item);
}
ksession.insert(event);
}
}
// run the rule evaluation
ksession.fireAllRules();
}
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
@Override
protected String getName() {
return "Rule Evaluation Service";
}
/**
* {@inheritDoc}
*/
public void handleEvent(Event event) {
String itemName = (String) event.getProperty("item");
String topic = event.getTopic();
String[] topicParts = topic.split(TOPIC_SEPERATOR);
if(!(topicParts.length > 2) || !topicParts[0].equals(TOPIC_PREFIX)) {
return; // we have received an event with an invalid topic
}
String operation = topicParts[1];
if(operation.equals(EventType.COMMAND.toString())) {
Command command = (Command) event.getProperty("command");
if(command!=null) receiveCommand(itemName, command);
}
}
static private final class RuleEventListener implements SystemEventListener {
private final Logger logger = LoggerFactory.getLogger(SystemEventListener.class);
public void warning(String message, Object object) {
logger.warn(message);
}
public void warning(String message) {
logger.warn(message);
}
public void info(String message, Object object) {
logger.info(message);
}
public void info(String message) {
logger.info(message);
}
public void exception(String message, Throwable e) {
logger.error(message, e);
}
public void exception(Throwable e) {
logger.error(e.getLocalizedMessage(), e);
}
public void debug(String message, Object object) {
logger.debug(message);
}
public void debug(String message) {
logger.debug(message);
}
}
}