/*
* 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.soa.esb.listeners.ListenerTagNames.RULE_RELOAD_TAG;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.CONTINUE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.DECISION_TABLE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.DISPOSE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.IMPL_CLASS;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_AGENT_PROPERTIES;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_AUDIT_FILE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_AUDIT_INTERVAL;
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.RULE_EVENT_PROCESSING_TYPE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_FIRE_METHOD;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_MAX_THREADS;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_MULTITHREAD_EVALUATION;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.STATEFUL;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.FIRE_ALL_RULES;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.FIRE_UNTIL_HALT;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.drools.runtime.Channel;
import org.jboss.soa.esb.Configurable;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.common.Configuration;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.services.rules.RuleInfo;
import org.jboss.soa.esb.services.rules.RuleService;
import org.jboss.soa.esb.services.rules.ServiceChannel;
import org.jboss.soa.esb.services.rules.StatefulRuleInfo;
import org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue;
import org.jboss.soa.esb.util.ClassUtil;
/**
* RuleServiceCallHelper is a class for calling
* methods on a {@link RuleService} implementation.
* </p>
*
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
*
*/
public class RuleServiceCallHelper
{
private static Logger logger = Logger.getLogger(RuleServiceCallHelper.class);
/**
* The default continue state expected.
*/
private static final boolean DEFAULT_CONTINUE_STATE;
/**
* The {@link RuleService} implementation to use.
*/
private RuleService ruleService;
/**
* The rule set.
*/
private String ruleSet;
/**
* The optional rule language. (DSL)
*/
private String ruleLanguage;
/**
* True if a ruleSet is being used.
*/
private boolean useRuleSet;
/**
* The decision table to be use, if configured.
*/
private String decisionTable;
private boolean useDecisionTable;
/**
* The rule agent to be use, if configured.
*/
private String ruleAgent;
private boolean useRuleAgent;
/**
* Should this be a stateful rules execution.
*/
private boolean stateful;
/**
* Controll the reloading of rules.
*/
private boolean ruleReload;
// audit parameters
private String auditType;
private String auditFile;
private Integer auditInterval;
// clock and event processing parameters
private String clockType;
private String eventProcessingType;
private Boolean multithreadEvaluation;
private Integer maxThreads;
// channels (exit points)
private Map<String,Channel> channels;
// rule fire method
private String ruleFireMethod;
public RuleServiceCallHelper(final ConfigTree config) throws ConfigurationException
{
final String ruleServiceImpl = config.getAttribute(IMPL_CLASS.getName(), DroolsRuleService.class.getName());
try
{
ruleService = RuleServiceFactory.getRuleService( ruleServiceImpl );
ruleService.setConfigTree( config );
}
catch (RuleServiceException e)
{
throw new ConfigurationException("Could not create RuleService", e);
}
ruleSet = config.getAttribute(ListenerTagNames.RULE_SET_TAG);
ruleLanguage = config.getAttribute(ListenerTagNames.RULE_LANGUAGE_TAG);
String ruleReloadStr = config.getAttribute(ListenerTagNames.RULE_RELOAD_TAG);
if (ruleReloadStr != null && "true".equals(ruleReloadStr))
{
ruleReload = true;
}
auditType = config.getAttribute( RULE_AUDIT_TYPE.getName() );
auditFile = config.getAttribute( RULE_AUDIT_FILE.getName() );
String rai = config.getAttribute( RULE_AUDIT_INTERVAL.getName() );
auditInterval = (rai != null) ? Integer.valueOf(rai) : null;
clockType = config.getAttribute( RULE_CLOCK_TYPE.getName() );
eventProcessingType = config.getAttribute( RULE_EVENT_PROCESSING_TYPE.getName() );
String rme = config.getAttribute( RULE_MULTITHREAD_EVALUATION.getName() );
multithreadEvaluation = (rme != null) ? Boolean.valueOf(rme) : null;
String rmt = config.getAttribute( RULE_MAX_THREADS.getName() );
maxThreads = (rmt != null) ? Integer.valueOf(rmt) : null;
channels = getChannels(config);
ruleFireMethod = config.getAttribute( RULE_FIRE_METHOD.getName() );
useRuleSet = ruleSet != null;
if (ruleSet == null)
{
// Extract the decision table from the configuration(if configured).
decisionTable = config.getAttribute( DECISION_TABLE.getName() );
useDecisionTable = decisionTable != null;
if (useDecisionTable == false)
{
// Extract the ruleAgent from the configuration(if configured).
ruleAgent = config.getAttribute( RULE_AGENT_PROPERTIES.getName() );
useRuleAgent = true;
if(logger.isDebugEnabled())
{
if (ruleAgent != null && ruleReload)
{
logger.debug("'" + RULE_RELOAD_TAG + "' is specified on the same configuration as a Rule Agent configuration is specified. Ignoring the '" + RULE_RELOAD_TAG + "' configuration.");
}
}
}
}
stateful = Boolean.valueOf(config.getAttribute(STATEFUL.getName())).booleanValue();
}
@SuppressWarnings("unchecked")
static Map<String,Channel> getChannels(final ConfigTree config) throws ConfigurationException
{
Map<String,List<Channel>> channel_list_map = new HashMap<String,List<Channel>>();
ConfigTree[] send_to_cfgs = config.getChildren("send-to");
for (ConfigTree send_to_cfg : send_to_cfgs)
{
String channel_name = send_to_cfg.getRequiredAttribute("channel-name");
List<Channel> channel_list = channel_list_map.get(channel_name);
if (channel_list == null)
{
channel_list = new ArrayList<Channel>();
channel_list_map.put(channel_name, channel_list);
}
String channel_class_name = send_to_cfg.getAttribute("channel-class", ServiceChannel.class.getName());
Channel channel;
try
{
Class<Channel> channel_class = (Class<Channel>)ClassUtil.forName(channel_class_name, RuleServiceCallHelper.class);
channel = channel_class.newInstance();
if (channel instanceof Configurable)
{
((Configurable)channel).setConfiguration(send_to_cfg);
}
channel_list.add(channel);
}
catch (ClassNotFoundException cnfe)
{
throw new ConfigurationException("could not find channel-class: " + channel_class_name, cnfe);
}
catch (Exception nsme)
{
throw new ConfigurationException("problem instantiating channel-class: " + channel_class_name, nsme);
}
}
Map<String,Channel> channel_map = new HashMap<String,Channel>();
for (Entry<String,List<Channel>> entry : channel_list_map.entrySet())
{
String channel_name = entry.getKey();
final List<Channel> channel_list = entry.getValue();
int channel_list_size = channel_list.size();
if (channel_list_size == 1)
{
channel_map.put(channel_name, channel_list.get(0));
}
else if (channel_list_size > 1)
{
channel_map.put(channel_name, new Channel() {
public void send(Object object) {
for (Channel channel : channel_list) {
channel.send(object);
}
}
});
}
}
return channel_map;
}
public Message executeRulesService(final RuleInfo ruleInfo, final Message message) throws RuleServiceException
{
if (isStateful())
{
return executeStateful(ruleInfo, message);
}
else
{
return executeStateless(ruleInfo, message);
}
}
public Message executeStateless(final RuleInfo ruleInfo, final Message message) throws RuleServiceException
{
if (useRuleSet)
{
return ruleService.executeStatelessRules(ruleInfo, message);
}
else if (useDecisionTable)
{
return ruleService.executeStatelessRulesFromDecisionTable(ruleInfo, message);
}
else if (useRuleAgent)
{
return ruleService.executeStatelessRulesFromRuleAgent(ruleInfo, message);
}
else
{
throw new RuleServiceException( "One of '" + ListenerTagNames.RULE_SET_TAG + "', '" + DECISION_TABLE.getName() + "', or ' " + RULE_AGENT_PROPERTIES.getName() + "'must be specified as properties in jboss-esb.xml");
}
}
public Message executeStateful(final RuleInfo ruleInfo, final Message message) throws RuleServiceException
{
// validates ruleFireMethod configuration
isFireUntilHalt(ruleInfo);
final boolean dispose = isDispose(message);
// validates (explicitContinueState && (diposeProperty != null))
final boolean explicitContinueState = isExplicitContinueState(message);
final boolean continueState = explicitContinueState || isContinueStateOrDefault(message);
final StatefulRuleInfo statefulRuleInfo = new StatefulRuleInfoImpl(ruleInfo, dispose, continueState);
if (explicitContinueState)
{
return ruleService.continueStatefulRulesExecution(statefulRuleInfo, message);
}
else
{
if (useRuleSet)
{
return ruleService.executeStatefulRules(statefulRuleInfo, message);
}
else if (useDecisionTable)
{
return ruleService.executeStatefulRulesFromDecisionTable(statefulRuleInfo, message);
}
else if (useRuleAgent)
{
return ruleService.executeStatefulRulesFromRuleAgent(statefulRuleInfo, message);
}
else
{
throw new RuleServiceException( "One of '" + ListenerTagNames.RULE_SET_TAG + "', '" + DECISION_TABLE.getName() + "', or ' " + RULE_AGENT_PROPERTIES.getName() + "'must be specified as properties in jboss-esb.xml");
}
}
}
public String determineRuleSource()
{
if (useRuleSet)
{
return ruleSet;
}
else if (useDecisionTable)
{
return decisionTable;
}
else
{
return ruleAgent;
}
}
public boolean isUseRuleSet()
{
return useRuleSet;
}
public boolean isUseDecisionTable()
{
return useDecisionTable;
}
public boolean isUseRuleAgent()
{
return useRuleAgent;
}
public boolean isStateful()
{
return stateful;
}
public String getRuleSet()
{
return ruleSet;
}
public String getRuleLanguage()
{
return ruleLanguage;
}
public String getDecisionTable()
{
return decisionTable;
}
public String getRuleAgent()
{
return ruleAgent;
}
public boolean isRuleReload()
{
return ruleReload;
}
public String getAuditType()
{
return auditType;
}
public String getAuditFile()
{
return auditFile;
}
public int getAuditInterval()
{
return auditInterval;
}
public String getClockType()
{
return clockType;
}
public String getEventProcessingType()
{
return eventProcessingType;
}
public Boolean getMultithreadEvaluation()
{
return multithreadEvaluation;
}
public Integer getMaxThreads()
{
return maxThreads;
}
public Map<String,Channel> getChannels()
{
return channels;
}
public String getRuleFireMethod()
{
return ruleFireMethod;
}
public RuleInfoBuilder getRuleInfoBuilder()
{
final RuleInfoBuilder builder = new RuleInfoBuilder(determineRuleSource());
builder.dslSource(ruleLanguage);
builder.reload(ruleReload);
builder.auditType(auditType);
builder.auditFile(auditFile);
builder.auditInterval(auditInterval);
builder.clockType(clockType);
builder.eventProcessingType(eventProcessingType);
builder.multithreadEvaluation(multithreadEvaluation);
builder.maxThreads(maxThreads);
builder.channels(channels);
builder.ruleFireMethod(ruleFireMethod);
return builder;
}
static boolean isFireUntilHalt(final RuleInfo ruleInfo) throws RuleServiceException
{
if (ruleInfo != null)
{
String ruleFireMethodConfig = ruleInfo.getRuleFireMethod();
if (ruleFireMethodConfig != null)
{
StringValue ruleFireMethodValue = RULE_FIRE_METHOD.getStringValue(ruleFireMethodConfig);
if (FIRE_UNTIL_HALT.equals(ruleFireMethodValue))
{
return true;
}
else if (!FIRE_ALL_RULES.equals(ruleFireMethodValue))
{
throw new RuleServiceException("unrecognized " + RULE_FIRE_METHOD.getName() + ": " + ruleFireMethodConfig);
}
}
}
return false;
}
private static boolean isDispose(final Message message)
{
Object disposeProperty = message.getProperties().getProperty(DISPOSE.getName());
return (disposeProperty != null) && Boolean.parseBoolean(disposeProperty.toString());
}
private static boolean isExplicitContinueState(final Message message) throws RuleServiceException
{
final Object continueProperty = message.getProperties().getProperty(CONTINUE.getName());
final boolean isExplicitContinueState = (continueProperty) != null ? Boolean.parseBoolean(continueProperty.toString()) : false;
if (isExplicitContinueState)
{
final Object disposeProperty = message.getProperties().getProperty(DISPOSE.getName());
if (disposeProperty == null)
{
throw new RuleServiceException(
"The [" + DISPOSE.getName() + "] property must be specified when the [" +
CONTINUE.getName() + "] property is specified as true. This is required as it is" +
" important that the rules working memory be disposed or memory leaks can occur." );
}
}
return isExplicitContinueState;
}
private static boolean isContinueStateOrDefault(final Message message) throws RuleServiceException
{
final Object continueProperty = message.getProperties().getProperty(CONTINUE.getName());
if (continueProperty != null)
{
return Boolean.parseBoolean(continueProperty.toString());
}
else
{
return DEFAULT_CONTINUE_STATE;
}
}
static
{
DEFAULT_CONTINUE_STATE = Boolean.parseBoolean(Configuration.getRulesContinueState());
}
}