/*
* 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.services.rules.RuleServicePropertiesNames.RULE_EVENT_PROCESSING_TYPE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.CLOUD;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.STREAM;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.drools.ChangeSet;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseConfiguration;
import org.drools.KnowledgeBaseFactory;
import org.drools.RuntimeDroolsException;
import org.drools.agent.KnowledgeAgent;
import org.drools.agent.KnowledgeAgentConfiguration;
import org.drools.agent.KnowledgeAgentFactory;
import org.drools.builder.DecisionTableConfiguration;
import org.drools.builder.DecisionTableInputType;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderConfiguration;
import org.drools.builder.KnowledgeBuilderErrors;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.builder.conf.ClassLoaderCacheOption;
import org.drools.compiler.DroolsParserException;
import org.drools.conf.EventProcessingOption;
import org.drools.conf.MaxThreadsOption;
import org.drools.conf.MultithreadEvaluationOption;
import org.drools.io.ResourceFactory;
import org.jboss.internal.soa.esb.services.rules.util.RulesClassLoader;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.services.rules.RuleInfo;
import org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue;
import org.jboss.soa.esb.util.ClassUtil;
/**
* A helper class, it returns rulebases based on various methods of creating them.
* <p/>
*
* @author jdelong@redhat.com
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
*
*/
public class DroolsRuleBaseHelper {
private static Logger logger = Logger.getLogger(DroolsRuleBaseHelper.class);
private DroolsRuleBaseHelper() {}
/**
* Factory method that returns an instance of this class.
* </p>
* The current implementation returns a new instance of this
* class for every call.
*
* @return {@link DroolsRuleBaseHelper}
*/
public static DroolsRuleBaseHelper getInstance()
{
return new DroolsRuleBaseHelper();
}
/**
* Creates a rulebase using rules and dsl from files
* <p/>
*
* @param ruleFile -
* file name which contains the Drools rules. Can be a file on the local
* filesystem, a file on the classpath or an URL. Must not be null.
* @param dsl -
* file containing the Drools Domain Specific Language (dsl)
* @throws RuleServiceException
* @throws DroolsParserException -
* if an exception occurs during Drools package building
* @throws IOException -
* if an IOException occurs during Drools package building
* @throws RuleServiceException -
* if the ruleFile cannot be located or if it was not possible to
* add the package to the RuleBase.
*/
public KnowledgeBase createRuleBaseFromRuleFiles(
final RuleInfo ruleInfo ) throws RuleServiceException, RuntimeDroolsException
{
final String drl = ruleInfo.getRuleSource();
final String dsl = ruleInfo.getDslSource();
assertNotNull( drl, "ruleFile" );
KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
KnowledgeBase ruleBase;
InputStream dslInputStream = null;
InputStream drlInputStream = null;
try
{
if (dsl == null)
{
drlInputStream = getRulesInputStream(drl);
builder.add(ResourceFactory.newInputStreamResource(drlInputStream), ResourceType.DRL);
ruleBase = getNewRuleBaseWithPackage(builder, ruleInfo);
}
else
{
dslInputStream = getRulesInputStream(dsl);
builder.add(ResourceFactory.newInputStreamResource(dslInputStream), ResourceType.DSL);
drlInputStream = getRulesInputStream(drl);
builder.add(ResourceFactory.newInputStreamResource(drlInputStream), ResourceType.DSLR);
ruleBase = getNewRuleBaseWithPackage(builder, ruleInfo);
}
}
finally
{
safeClose(drlInputStream);
safeClose(dslInputStream);
}
return ruleBase;
}
/**
* Reads the rules and dsl from files and returning as a string
*
* @param ruleFile -
* file name which contains the Drools rules. Can be a file on the local
* filesystem, a file on the classpath or an URL. Must not be null.
* @param dsl -
* file containing the Drools Domain Specific Language (dsl)
* @return String -
* String representation of the rules
* @throws IOException -
* if an IOException occurs during Drools package building
* @throws RuleServiceException -
* if the ruleFile cannot be located or if it was not possible to
* add the package to the RuleBase.
* @throws RuleServiceException
*/
public String getRulesAsString(
final String ruleFile,
final String dsl) throws RuleServiceException
{
assertNotNull( ruleFile, "rulefile" );
final String rules = getFileContents( ruleFile );
return dsl == null ? rules : rules + getFileContents( dsl );
}
/**
* Reading the rules from a decision table.
* <p/>
* @param decisionTable -
* file name which contains the Drools decision table. Can be a file on the local
* filesystem, a file on the classpath or an URL. Must not be null.
* @throws RuleServiceException
* @throws DroolsParserException -
*
* @throws IOException -
*
* @throws RuleServiceException -
*/
public KnowledgeBase createRuleBaseFromDecisionTable(
final RuleInfo ruleInfo ) throws RuleServiceException
{
final String decisionTable = ruleInfo.getRuleSource();
assertNotNull( decisionTable, "decisionTable" );
DecisionTableConfiguration dtc = KnowledgeBuilderFactory.newDecisionTableConfiguration();
String decisionTable_tlc = decisionTable.trim().toLowerCase();
if (decisionTable_tlc.endsWith(".xls"))
{
dtc.setInputType(DecisionTableInputType.XLS);
}
else if (decisionTable_tlc.endsWith(".csv"))
{
dtc.setInputType(DecisionTableInputType.CSV);
}
KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
InputStream dtInputStream = null;
KnowledgeBase ruleBase;
try
{
dtInputStream = getRulesInputStream(decisionTable);
builder.add(ResourceFactory.newInputStreamResource(dtInputStream), ResourceType.DTABLE, dtc);
ruleBase = getNewRuleBaseWithPackage(builder, ruleInfo);
}
finally
{
safeClose(dtInputStream);
}
return ruleBase;
}
/**
* This shows how rules are loaded up from a deployed package.
*/
public KnowledgeAgent createRuleAgent(final RuleInfo ruleInfo) throws RuleServiceException
{
final DroolsRuleAgentHelper drah = new DroolsRuleAgentHelper(ruleInfo);
final Properties properties = drah.getProperties();
final ClassLoader classLoader = drah.getClassLoader();
final String name = drah.getName();
final ChangeSet changeSet = drah.getChangeSet();
final KnowledgeBaseConfiguration kbaseConfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(properties, classLoader);
final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(kbaseConfig);
final KnowledgeAgentConfiguration kagentConfig = KnowledgeAgentFactory.newKnowledgeAgentConfiguration(properties);
final KnowledgeBuilderConfiguration kbuilderConfig = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(properties, classLoader);
final KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent(name, kbase, kagentConfig, kbuilderConfig);
kagent.setSystemEventListener(new LogAgentEventListener(name));
kagent.applyChangeSet(changeSet);
return kagent;
}
private String getFileContents( final String fileName ) throws RuleServiceException
{
InputStream inputStream = getRulesInputStream( fileName );
try
{
return StreamUtils.readStreamString( inputStream, "UTF-8" );
}
catch ( final UnsupportedEncodingException e)
{
throw new RuleServiceException("Could not read from file [" + fileName + "].", e);
}
finally
{
safeClose( inputStream );
}
}
// private instance methods
private InputStream getRulesInputStream(final String rulesFile) throws RuleServiceException
{
InputStream resourceAsStream = ClassUtil.getResourceAsStream( "/" + rulesFile, getClass() );
if ( resourceAsStream == null )
throw new RuleServiceException("Could not locate file [" + rulesFile + "], neither as a file on the local filesystem, on the classpath nor as a URL.");
else
return resourceAsStream;
}
private KnowledgeBase getNewRuleBaseWithPackage( final KnowledgeBuilder builder, final RuleInfo ruleInfo ) throws RuleServiceException, RuleServiceBuilderException
{
final KnowledgeBuilderErrors errors = builder.getErrors();
if ((errors != null) && (errors.size() > 0))
{
throw new RuleServiceException("Generation raised the following errors: " + errors) ;
}
// If we don't use this config, then all rules' LHS object conditions will not match on .esb redeploys!
// (since objects are only equal if their ClassLoaders are also equal - and they're not on redeploys..)
final Properties properties = new Properties();
properties.setProperty(ClassLoaderCacheOption.PROPERTY_NAME, Boolean.FALSE.toString());
final ClassLoader classLoader = new RulesClassLoader(ruleInfo.getRuleSource());
KnowledgeBaseConfiguration kbaseConfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(properties, classLoader);
// CEP
StringValue eventProcessingType = RULE_EVENT_PROCESSING_TYPE.getStringValue(ruleInfo.getEventProcessingType());
if (STREAM.equals(eventProcessingType))
{
kbaseConfig.setOption(EventProcessingOption.STREAM);
}
else if (CLOUD.equals(eventProcessingType))
{
kbaseConfig.setOption(EventProcessingOption.CLOUD);
}
Boolean multithreadEvaluation = ruleInfo.getMultithreadEvaluation();
if (multithreadEvaluation != null) {
MultithreadEvaluationOption meo = multithreadEvaluation.booleanValue() ? MultithreadEvaluationOption.YES : MultithreadEvaluationOption.NO;
kbaseConfig.setOption(meo);
// only pertinent if multithreadEvaluation == true
Integer maxThreads = ruleInfo.getMaxThreads();
if (maxThreads != null) {
kbaseConfig.setOption(MaxThreadsOption.get(maxThreads.intValue()));
}
}
KnowledgeBase ruleBase = KnowledgeBaseFactory.newKnowledgeBase(kbaseConfig);
try
{
ruleBase.addKnowledgePackages(builder.getKnowledgePackages());
}
catch (final Exception ex)
// need to catch Exception as RuleBase.addPackage throws it
{
throw new RuleServiceException(ex.getMessage(), ex);
}
if ( builder.hasErrors() )
{
throw new RuleServiceBuilderException( "PackageBuilder generated errors: ", builder.getErrors() );
}
return ruleBase;
}
// private static methods
private static void safeClose( final InputStream is )
{
try
{
if (is != null)
{
is.close();
}
}
catch (final IOException e)
{
logger.warn("Caught an IOException while trying to close as inputstream.", e );
}
}
private static void assertNotNull( final String argumentValue, final String argumentName )
{
if( argumentValue == null )
throw new NullPointerException("[" + argumentName + "] argument must not be null");
}
}