/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.internal.soa.esb.services.rules;
import static org.jboss.internal.soa.esb.services.rules.RuleServiceCallHelper.isFireUntilHalt;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_AUDIT_TYPE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_CLOCK_TYPE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.CONSOLE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.FILE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.PSEUDO;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.REALTIME;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.THREADED_FILE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.drools.ClockType;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.common.EventFactHandle;
import org.drools.common.InternalFactHandle;
import org.drools.event.KnowledgeRuntimeEventManager;
import org.drools.impl.EnvironmentFactory;
import org.drools.logger.KnowledgeRuntimeLogger;
import org.drools.logger.KnowledgeRuntimeLoggerFactory;
import org.drools.runtime.Channel;
import org.drools.runtime.Globals;
import org.drools.runtime.KnowledgeRuntime;
import org.drools.runtime.KnowledgeSessionConfiguration;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.StatelessKnowledgeSession;
import org.drools.runtime.conf.ClockTypeOption;
import org.drools.runtime.rule.WorkingMemoryEntryPoint;
import org.jboss.internal.soa.esb.services.rules.util.RulesContext;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.services.rules.RuleInfo;
import org.jboss.soa.esb.services.rules.StatefulRuleInfo;
import org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue;
/**
* Drools rule base state used to execute and cleanup rule bases. Some parts have been extracted from the DroolsRuleService.
*
* @author <a href='mailto:Kevin.Conner@jboss.com'>Kevin Conner</a>
* @author jdelong@redhat.com
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
* @author dward at jboss.org
*/
class DroolsRuleBaseState
{
/**
* The logger for this state.
*/
private static Logger LOGGER = Logger.getLogger(DroolsRuleBaseState.class);
/**
* The rule base associated with this state.
*/
private final KnowledgeBase ruleBase;
/**
* The disposed flag. If set, stateful sessions are automatically disposed.
*/
private transient boolean disposed;
/**
* The stateful session.
*/
private StatefulKnowledgeSession statefulSession;
/**
* The stateful runtime logger
*/
private KnowledgeRuntimeLogger statefulRuntimeLogger;
/**
* The stateful fireUntilHalt thread.
*/
private Thread statefulFireUntilHaltThread;
/**
* The stateful session lock.
*/
private final Lock statefulSessionLock = new ReentrantLock();
/**
* The stateful session count lock.
*/
private final Lock statefulSessionCountLock = new ReentrantLock();
/**
* The number of sessions queued for the stateful session.
*/
private int statefulSessionCount;
/**
* The stateless sessions.
*/
private final ConcurrentLinkedQueue<StatelessKnowledgeSession> statelessSessions = new ConcurrentLinkedQueue<StatelessKnowledgeSession>();
/**
* Construct the rule base state.
* @param ruleBase The associated rule base.
*/
DroolsRuleBaseState(final KnowledgeBase ruleBase)
{
this.ruleBase = ruleBase;
}
/**
* Get the rule base associated with this state.
* @return The rule base.
*/
KnowledgeBase getRuleBase()
{
return ruleBase;
}
/**
* Execute rules using using the Stateless API
*
* @param ruleInfo - Stateless holder containing execution parameters.
* @param message - Message that is updated with the results.
*
* @return Message - with updated objects.
*/
Message executeStatelessRules(
final RuleInfo ruleInfo,
final Message message)
{
final Map<String,Object> globals = ruleInfo.getGlobals();
final List<Object> objectList = ruleInfo.getDefaultFacts();
final String sid;
final StatelessKnowledgeSession head = statelessSessions.poll();
final StatelessKnowledgeSession statelessSession;
if (head != null)
{
statelessSession = head;
sid = getId(statelessSession);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("reusing old stateles session [" + sid + "]");
}
}
else
{
statelessSession = ruleBase.newStatelessKnowledgeSession();
sid = getId(statelessSession);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("created new stateless session [" + sid + "]");
}
}
RulesContext.clearContext();
try
{
final List<Object> facts = new ArrayList<Object>();
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("adding Message into fact list for stateless session [" + sid + "]");
}
facts.add(message);
if (objectList != null)
{
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("adding default facts into fact list for stateless session [" + sid + "]");
}
facts.addAll(objectList);
}
KnowledgeRuntimeLogger statelessRuntimeLogger = getRuntimeLogger(ruleInfo, statelessSession);
if (statelessRuntimeLogger != null && LOGGER.isDebugEnabled())
{
LOGGER.debug("created new runtime logger [" + getId(statelessRuntimeLogger) + "]");
}
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("setting globals delegate for stateless session [" + sid + "]");
}
statelessSession.getGlobals().setDelegate(new StatelessGlobals(globals));
try
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling execute(Iterable) on stateless session [" + sid + "]");
}
statelessSession.execute(facts);
}
finally
{
statelessSession.getGlobals().setDelegate(null);
if (statelessRuntimeLogger != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling close() on runtime logger [" + getId(statelessRuntimeLogger) + "]");
}
statelessRuntimeLogger.close();
}
}
}
finally
{
RulesContext.clearContext();
statelessSessions.add(statelessSession);
}
return message;
}
/**
* Execute rules using using the Stateful API
*
* @param ruleInfo - Stateful holder containing execution parameters.
* @param message - Message that is updated with the results.
*
* @return Message - with updated objects.
*/
Message executeStatefulRules(
final StatefulRuleInfo ruleInfo,
final Message message ) throws RuleServiceException
{
final boolean fireUntilHalt = isFireUntilHalt(ruleInfo);
final boolean dispose = ruleInfo.dispose();
final boolean continueState = ruleInfo.continueState();
RulesContext.clearContext();
statefulSessionCountLock.lock();
statefulSessionCount++;
statefulSessionCountLock.unlock();
try
{
statefulSessionLock.lock();
try
{
if (statefulSession != null && !continueState)
{
final String ssid = getId(statefulSession, statefulSessionCount);
final StatefulKnowledgeSession disposedStatefulSession = statefulSession;
final Thread haltedStatefulFireUntilHaltThread = statefulFireUntilHaltThread;
final KnowledgeRuntimeLogger closedStatefulRuntimeLogger = statefulRuntimeLogger;
statefulSession = null;
statefulFireUntilHaltThread = null;
statefulRuntimeLogger = null;
// Maybe halt the session
if (haltedStatefulFireUntilHaltThread != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling halt on stateful session [" + ssid + "] - no continue set");
}
disposedStatefulSession.halt();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("joining thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]");
}
try
{
haltedStatefulFireUntilHaltThread.join();
}
catch (InterruptedException ie)
{
LOGGER.error("interrupted thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]", ie);
}
}
// Always dispose the session
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling dispose() on stateful session [" + ssid + "] - no continue set");
}
disposedStatefulSession.dispose();
// Maybe close the logger
if (closedStatefulRuntimeLogger != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling close() on runtime logger [" + getId(closedStatefulRuntimeLogger, statefulSessionCount) + "] - no continue set");
}
closedStatefulRuntimeLogger.close();
}
}
final String ssid;
final boolean isStatefulSessionNew;
if (statefulSession == null)
{
isStatefulSessionNew = true;
KnowledgeSessionConfiguration statefulSessionConfiguration = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
setClockType(ruleInfo, statefulSessionConfiguration);
statefulSession = ruleBase.newStatefulKnowledgeSession(statefulSessionConfiguration, EnvironmentFactory.newEnvironment());
ssid = getId(statefulSession, statefulSessionCount);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("created new stateful session [" + ssid + "]");
}
setChannels(ruleInfo, statefulSession);
statefulRuntimeLogger = getRuntimeLogger(ruleInfo, statefulSession);
if (statefulRuntimeLogger != null && LOGGER.isDebugEnabled())
{
LOGGER.debug("created new runtime logger [" + getId(statefulRuntimeLogger, statefulSessionCount) + "]");
}
}
else
{
isStatefulSessionNew = false;
ssid = getId(statefulSession, statefulSessionCount);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("reusing old stateful session [" + ssid + "]");
}
}
try
{
final Map<String, Object> globals = ruleInfo.getGlobals();
if (globals != null)
{
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("calling setGlobal(String,Object) on stateful session [" + ssid + "] for each global");
}
final Set<Entry<String, Object>> entrySet = globals.entrySet();
for(Entry<String, Object> entry : entrySet)
{
statefulSession.setGlobal( entry.getKey(), entry.getValue() );
}
}
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("inserting Message on stateful session [" + ssid + "]");
}
// Always insert the ESB Message object.
InternalFactHandle handle = (InternalFactHandle)statefulSession.insert(message);
if (handle.isEvent() && LOGGER.isDebugEnabled())
{
EventFactHandle ef = (EventFactHandle)handle;
LOGGER.debug("event [" + ef.getObject().getClass().getName() + "], startTimeStamp [" + ef.getStartTimestamp() + "]");
}
// Always insert the default facts (into the main WorkingMemory; no entry-point specified)
final List<Object> defaultFacts = ruleInfo.getDefaultFacts();
if (defaultFacts != null)
{
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("calling insert(Object) on stateful session [" + ssid + "] for each default fact");
}
for(Object object : defaultFacts)
{
statefulSession.insert(object);
}
}
// Maybe insert entry point facts (into a named WorkingMemoryEntryPoint)
final Map<String,List<Object>> facts = ruleInfo.getFacts();
if (facts != null)
{
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("calling insert(Object) on stateful session [" + ssid + "] for each entry point fact");
}
for(Entry<String, List<Object>> entry : facts.entrySet())
{
String entryPointName = entry.getKey();
// Insert objects that have explicitly specified an entry-point.
WorkingMemoryEntryPoint wmep = statefulSession.getWorkingMemoryEntryPoint(entryPointName);
if (wmep == null)
{
throw new RuleServiceException("The entry-point '" + entryPointName + "' was not found in the current stateful session. Please double check your rules source");
}
for(Object fact : entry.getValue())
{
wmep.insert(fact);
}
}
}
// Fire stateful rules.
if (!fireUntilHalt)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling fireAllRules() on stateful session [" + ssid + "]");
}
statefulSession.fireAllRules();
}
else if (isStatefulSessionNew)
{
final String threadName = new StringBuilder()
.append(getClass().getSimpleName())
.append(":fireUntilHalt(")
.append(ssid)
.append(")")
.toString();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("spawning fireUntilHalt() on stateful session [" + ssid + "] in thread [" + threadName + "]");
}
final ClassLoader goodClassLoader = Thread.currentThread().getContextClassLoader();
statefulFireUntilHaltThread = new Thread(new Runnable() {
public void run() {
Thread thread = Thread.currentThread();
ClassLoader origClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(goodClassLoader);
try {
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling fireUntilHalt() on stateful session [" + ssid + "] in thread [" + threadName + "]");
}
statefulSession.fireUntilHalt();
} catch (NullPointerException npe) {
LOGGER.warn("fireUntilHalt() not called on stateful session [" + ssid + "] in thread [" + threadName + "] - already halt()ed and dispose()d: " + npe.getMessage());
} finally {
thread.setContextClassLoader(origClassLoader);
}
}
});
statefulFireUntilHaltThread.setName(threadName);
statefulFireUntilHaltThread.setDaemon(true);
statefulFireUntilHaltThread.start();
}
else if (LOGGER.isDebugEnabled())
{
LOGGER.debug("rule firing unnecessary on stateful session [" + ssid + "] - was initially fireUntilHalt()");
}
}
finally
{
if (dispose)
{
final StatefulKnowledgeSession disposedStatefulSession = statefulSession;
final Thread haltedStatefulFireUntilHaltThread = statefulFireUntilHaltThread;
final KnowledgeRuntimeLogger closedStatefulRuntimeLogger = statefulRuntimeLogger;
statefulSession = null;
statefulFireUntilHaltThread = null;
statefulRuntimeLogger = null;
// Maybe halt the session
if (haltedStatefulFireUntilHaltThread != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling halt() on stateful session [" + ssid + "]");
}
disposedStatefulSession.halt();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("joining thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]");
}
try
{
haltedStatefulFireUntilHaltThread.join();
}
catch (InterruptedException ie)
{
LOGGER.error("interrupted thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]", ie);
}
}
// Always dispose the session
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling dispose() on stateful session [" + ssid + "]");
}
disposedStatefulSession.dispose();
// Maybe close the logger
if (closedStatefulRuntimeLogger != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling close() on runtime logger [" + getId(closedStatefulRuntimeLogger, statefulSessionCount) + "]");
}
closedStatefulRuntimeLogger.close();
}
}
}
}
finally
{
statefulSessionLock.unlock();
}
}
finally
{
RulesContext.clearContext();
statefulSessionCountLock.lock();
statefulSessionCount--;
if (disposed && statefulSessionCount == 0)
{
dispose();
}
statefulSessionCountLock.unlock();
}
return message;
}
void dispose()
{
statefulSessionCountLock.lock();
try
{
disposed = true;
if ((statefulSessionCount == 0) && (statefulSession != null))
{
final String ssid = getId(statefulSession, statefulSessionCount);
final StatefulKnowledgeSession disposedStatefulSession = statefulSession;
final Thread haltedStatefulFireUntilHaltThread = statefulFireUntilHaltThread;
final KnowledgeRuntimeLogger closedStatefulRuntimeLogger = statefulRuntimeLogger;
statefulSession = null;
statefulFireUntilHaltThread = null;
statefulRuntimeLogger = null;
// Maybe halt the session
if (haltedStatefulFireUntilHaltThread != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling halt() on stateful session [" + ssid + "]");
}
disposedStatefulSession.halt();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("joining thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]");
}
try
{
haltedStatefulFireUntilHaltThread.join();
}
catch (InterruptedException ie)
{
LOGGER.error("interrupted thread [" + haltedStatefulFireUntilHaltThread.getName() + "] for stateful session [" + ssid + "]", ie);
}
}
// Always dispose the session
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling dispose() on stateful session [" + ssid + "]");
}
disposedStatefulSession.dispose();
// Maybe close the logger
if (closedStatefulRuntimeLogger != null)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("calling close() on runtime logger [" + getId(closedStatefulRuntimeLogger, statefulSessionCount) + "]");
}
closedStatefulRuntimeLogger.close();
}
}
statelessSessions.clear();
}
finally
{
statefulSessionCountLock.unlock();
}
}
private void setClockType(RuleInfo ruleInfo, KnowledgeSessionConfiguration statefulSessionConfiguration)
{
if (ruleInfo != null)
{
StringValue clockType = RULE_CLOCK_TYPE.getStringValue(ruleInfo.getClockType());
if (REALTIME.equals(clockType))
{
statefulSessionConfiguration.setOption(ClockTypeOption.get(ClockType.REALTIME_CLOCK.getId()));
}
else if (PSEUDO.equals(clockType))
{
statefulSessionConfiguration.setOption(ClockTypeOption.get(ClockType.PSEUDO_CLOCK.getId()));
}
}
}
private void setChannels(RuleInfo ruleInfo, KnowledgeRuntime session)
{
Map<String,Channel> channel_map = ruleInfo.getChannels();
if (channel_map != null)
{
for (Entry<String,Channel> channel_entry : channel_map.entrySet())
{
String channel_name = channel_entry.getKey();
Channel channel = channel_entry.getValue();
if (channel_name != null && channel != null)
{
session.registerChannel(channel_name, channel);
}
}
}
}
private KnowledgeRuntimeLogger getRuntimeLogger(RuleInfo ruleInfo, KnowledgeRuntimeEventManager session)
{
if (ruleInfo != null)
{
StringValue auditType = RULE_AUDIT_TYPE.getStringValue(ruleInfo.getAuditType());
if (CONSOLE.equals(auditType))
{
return KnowledgeRuntimeLoggerFactory.newConsoleLogger(session);
}
// If auditType is not defined and neither is ruleAuditFile, no auditing is done.
// If auditType is not defined but ruleAuditFile is, the assumption is THREADED_FILE.
boolean isFile = FILE.equals(auditType);
boolean isThreadedFile = THREADED_FILE.equals(auditType);
String auditFile = ruleInfo.getAuditFile();
if (isFile || isThreadedFile || auditFile != null)
{
if (auditFile == null)
{
auditFile = "event";
}
if (isFile)
{
return KnowledgeRuntimeLoggerFactory.newFileLogger(session, auditFile);
}
Integer auditInterval = ruleInfo.getAuditInterval();
if (auditInterval == null)
{
auditInterval = Integer.valueOf(1000);
}
return KnowledgeRuntimeLoggerFactory.newThreadedFileLogger(session, auditFile, auditInterval.intValue());
}
}
return null;
}
private final String getId(final Object object)
{
return String.valueOf(System.identityHashCode(object));
}
private final String getId(final Object object, final int count)
{
return new StringBuilder()
.append(getId(object))
.append(":")
.append(count)
.toString();
}
private static final class StatelessGlobals implements Globals
{
private final Map<String, Object> globals;
public StatelessGlobals(Map<String, Object> globals)
{
this.globals = new HashMap<String, Object>(globals);
}
public Object get(String identifier)
{
return globals.get(identifier);
}
public void set(String identifier, Object value)
{
this.globals.put(identifier, value);
}
public void setDelegate(Globals delegate) {}
}
}