package net.fp.rp.drools;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import net.fp.rp.common.exception.RpException;
import org.apache.log4j.Logger;
import org.drools.FactHandle;
import org.drools.IntegrationException;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.StatefulSession;
import org.drools.StatelessSession;
import org.drools.WorkingMemory;
import org.drools.compiler.DroolsParserException;
import org.drools.compiler.PackageBuilder;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.drools.rule.Package;
import org.springframework.core.io.ClassPathResource;
import org.xml.sax.SAXException;
/**
* Wrapper Class around the the JBoss Rules (Drools) Decision Table. This can be
* called by Spring
*
* This Class also caches the rules that are compiled from the Decision Table, both in
* the intermediate (drl) rule format , and as a serialised Java class.
*
* @see SpringDecisionTableLoaderTest for a work example of this class in action
* @author Paul Browne
*
* Copyright @link www.firstpartners.net/red
*/
public class SpringDecisionTableLoader {
private String drlFileLocation = null;
/**
* the name of the excel file that is our decision table
*/
private String excelFileLocation = null;
// Handle to the ExcelDecisionTable
private RuleBase excelRules = null;
/** Logger for this class and subclasses */
protected final Logger log = Logger.getLogger(getClass());
// Where serialised files are stored
private String serialFileLocation = null;
// The working Memory for a rule
private StatefulSession statefulSession = null;
/**
* executeDecisionTable
*
* @param javaBean -
* this should be same as the JavaBean expected by the Decision
* Table or an RuleCompileException will be thrown.
* @param useNewWorkingMemory -
* whether or not to setup a new working memory , otherwise use
* shared.Get a new working memory if requested. We do this as
* Spring by default makes the bean act as a singleton. Using the
* Shared working memory allows us to re-use previous results
* (faster) and preserves state
* @return javaBean - same Bean (Type and object reference) as the input,
* but with values updated as per the decision Table
* @throws RpException
*/
public Object executeDecisionTable(Object javaBean,
boolean useNewWorkingMemory) throws RpException {
// Local Variables
WorkingMemory localWorkingMemory = null;
// Make sure our ruleset is loaded
initialize();
// Get a new working memory if requested. We do this as Spring by
// default
// makes the bean act as a singleton. Using the Shared working memory
// allows us to
// re-use previous results (faster) and preserves state
if (useNewWorkingMemory) {
localWorkingMemory = getRuleBase().newStatefulSession();
} else {
localWorkingMemory = this.getSharedWorkingMemory();
}
// Place the Java Bean in the working memory , keep a handle to it
FactHandle currentHandle = localWorkingMemory.insert(javaBean);
// use Drools *without* any Java intervention
localWorkingMemory.fireAllRules();
// Remove the reference to this as part of the cleanup for later
localWorkingMemory.retract(currentHandle);
return javaBean;
}
/**
* Get the filename of the DRL Rules file , if any (optional)
*
* @return
*/
public String getDrlFileLocation() {
return drlFileLocation;
}
/**
* Set the filename of the DRL Rules file , if any (optional)
*
* @return
*/
public String getExcelFileLocation() {
return excelFileLocation;
}
/**
* @return Returns the excelFileName containing the Decision table , if any
* (optional)
*/
public String getExcelFileName() {
return excelFileLocation;
}
/**
* @return the Rulebase, if it has been loaded, null if it has not
*/
public RuleBase getRuleBase() {
return excelRules;
}
/**
* Searches for a file via the local filesystem , then via the Classpath
*
* @param fileName
* to search for
* @return InputStream containing the opened file
*/
private InputStream getFileIfExists(String fileName) throws RpException {
// Local Variables
InputStream rIStream = null;
log.debug("Searching for File name:" + fileName);
// The next line creates one execution of the process definition.
// After construction the process execution has one main path
// of execution (the root token) that is positioned in the
// startstate.-
try {
rIStream = new FileInputStream(fileName);
} catch (FileNotFoundException fnfe1) {
log
.debug("Could not find resource as file - attempting the classpath");
try {
ClassPathResource myClassPathReader = new ClassPathResource(
fileName);
rIStream = myClassPathReader.getInputStream();
log.debug("Found File via Classpath");
} catch (Exception fnfe2) {
log.debug("Returning Null as could not find file"+fileName+ "locally or on classpath");
//log.info("Could not find resource as file", fnfe1);
//log.info("Could not find resource on classpath", fnfe2);
// we will return null
}
}
return rIStream;
}
/**
* @return the serialFileLocation, where the Serialized (compiled) rulebase
* is stored, if it exists
*/
public String getSerialFileLocation() {
return serialFileLocation;
}
/**
* Load and setup the rule engine , if we have not already done so It is <b>
* not mandatory to call this before the executeCalculation() method, but it
* does ensure that rules have been loaded and compiled before we try to do
* the first
*
* @throws RPException
*/
public void initialize() throws RpException {
if (getRuleBase() == null) {
setRuleBase(loadRules());
}
if (getSharedWorkingMemory() == null) {
setSharedWorkingMemory(getRuleBase().newStatefulSession());
}
}
/**
* Overloaded method, calls load Rules , using the excel,drl and serial file
* locations as previously set using the accessor methods
*
* @return
* @throws RpException
*/
public RuleBase loadRules() throws RpException {
return loadRules(this.excelFileLocation, this.drlFileLocation,
this.serialFileLocation);
}
/**
* Loads a rule base , searching for the rules in the following order:
* <ol>
* <li>A serialised (pre compiled RuleBase), as specified in serialPath.
* This will be generated if it does not exist</li>
* <li>A set of rules from a .drl file, as specified in drlPath</li>
* <li>An Excel Decision Table, from a .xls file , as specified in
* excelPath. This is then compiled, saved as a serialised file and as a
* <i>sample</i> drl file under the name <i>drlPath-tmp</i> (sample file
* not used by program)</li>
* </ol>
* The first rules file found (from the above order) is the one used and the
* search terminated.
*
* @param excelPath
* @param drlPath
* @param serialPath
* @return RuleBase, using the Rules file that has been found
* @throws RpException
*/
public RuleBase loadRules(String excelPath, String drlPath,
String serialPath) throws RpException {
// Local Variables
RuleBase returnRules = null;
String drlPathTmp = drlPath + "-tmp";
// Try to use the Serialized Version
log.debug("Attempting to load pre-serialised Rules from " + serialPath);
returnRules = loadSerializedRulesFromFile(serialPath);
if (returnRules != null) {
log.info("Use pre-serialised Rulebase from file:" + serialPath);
} else {
// Try to use the Drl Version
log.debug("Attempting to load drl Rules from " + drlPath);
returnRules = loadDrlRulesFromFile(drlPath);
if (returnRules != null) {
log.info("Use drl Rulebase from file:" + drlPath);
log.info("Serializing rules to file " + serialPath
+ " for later use");
serializeRulesToFile(returnRules, serialPath);
} else {
// Use the Excel Version
log.debug("Attempting to load excel Rules from " + excelPath);
returnRules = loadRulesFromExcelDecisionTable(excelPath,
drlPathTmp);
if (returnRules != null) {
log.info("Use excel Rulebase from file:" + excelPath);
log.info("Serializing rules to file " + serialPath
+ " for later use");
serializeRulesToFile(returnRules, serialPath);
} else {
throw new RpException(
"Unable to find Rules in any form (Serial,Drl,Excel) at any location (file,classpath)");
}
}
}
return returnRules;
}
/**
* Load the Rules from an Excel Decision Table
*
* @param fileName
* @param drlPathTmp
* @return
* @throws RpException
*/
public RuleBase loadRulesFromExcelDecisionTable(String fileName,
String drlPathTmp) throws RpException {
try {
SpreadsheetCompiler converter = new SpreadsheetCompiler();
// get the file exists (via file or classpath)
InputStream iStream = getFileIfExists(excelFileLocation);
// null check
if (iStream == null) {
return null;
}
// Compile the rules
String drl = converter.compile(iStream, InputType.XLS);
if (drl != null) {
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl(new StringReader(drl));
Package pkg = builder.getPackage();
if (pkg != null) {
setRuleBase(RuleBaseFactory.newRuleBase());
getRuleBase().addPackage(pkg);
log.info("Saving tmpCopy to " + drlPathTmp);
outputDrlToFile(drl, drlPathTmp);
} else {
throw new RpException(
"Problem creating new rule base from Excel");
}
} else {
throw new RpException("Problem loading Excel Decision Tables");
}
} catch (IOException ie) {
// Wrap Exception and throw it
log.debug("Rethrowing", ie);
throw new RpException(ie);
} catch (SAXException saxe) {
// Wrap Exception and throw it
throw new RpException(saxe);
} catch (IntegrationException ie) {
// Wrap Exception and throw it
throw new RpException(ie);
} catch (DroolsParserException dpe) {
// Wrap Exception and throw it
throw new RpException(dpe);
} catch (RpException rpe) {
// just rethrow
throw rpe;
} catch (Exception e) {
// Handling generic 'Exception' is forced upon us by
// RuleBase.addPackage
// Wrap Exception and throw it
log.warn(e);
throw new RpException(e);
}
// Return the excel rules that we have set
return this.getRuleBase();
}
/**
* Saves the drl string to the given file name
*
* @param drl
* @param fileName
* @throws RpException
*/
public void outputDrlToFile(String drl, String fileName) throws RpException {
// Open the file , overwrite any existing contents
try {
FileWriter out = new FileWriter(fileName, false);
out.write(drl);
out.close();
log.debug("Put copy of DRL in file:" + fileName);
} catch (IOException ie) {
throw new RpException(ie);
}
}
/**
* Loads a Rule base from a given DRL file
*
* @param fileName
* to load
* @return RuleBase form of the drl file
* @throws RpException
*/
public RuleBase loadDrlRulesFromFile(String fileName) throws RpException {
// Local Variables
RuleBase returnRuleBase = null;
// get the file exists (via file or classpath)
InputStream iStream = getFileIfExists(fileName);
// null check
if (iStream == null) {
return null;
}
// Convert the Stream into a reader that Drools uses to parse files
InputStreamReader drl = new InputStreamReader(iStream);
try {
log.info("Reading Rules from drl:" + fileName);
PackageBuilder builder = new PackageBuilder();
// builder.addPackageFromDrl( new StringReader(drl) );
builder.addPackageFromDrl(drl);
Package pkg = builder.getPackage();
if (pkg != null) {
returnRuleBase = RuleBaseFactory.newRuleBase();
returnRuleBase.addPackage(pkg);
}
} catch (IOException ie) {
throw new RpException(ie);
} catch (DroolsParserException dpe) {
throw new RpException(dpe);
} catch (Exception e) {
throw new RpException(e);
}
return returnRuleBase;
}
/**
* Loads a pre-compiled (serialised) RuleBase from a file
*
* @param fileName
* @return
* @throws RpException
*/
public RuleBase loadSerializedRulesFromFile(String fileName)
throws RpException {
RuleBase returnRuleBase = null;
try {
InputStream inStream = getFileIfExists(fileName);
// null check
if (inStream == null) {
return null;
}
ObjectInputStream in = new ObjectInputStream(inStream);
returnRuleBase = (RuleBase) in.readObject();
in.close();
} catch (Exception e) {
log.info("Exception when loading serialised RuleBase", e);
}
return returnRuleBase;
}
/**
* Serialise a rulebase to a file
*
* @param output -
* the RuleBase to save
* @param fileName -
* to save the Rulebase as
*/
public void serializeRulesToFile(RuleBase output, String fileName) {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(fileName));
out.writeObject(output);
out.close();
} catch (Exception e) {
log.warn("Exception when serialising RuleBase", e);
}
}
/**
* Where the Drl Rules file is located (if any)
*
* @param drlFileLocation
*/
public void setDrlFileLocation(String drlFileLocation) {
this.drlFileLocation = drlFileLocation;
}
/**
* Where the Excel Decision file table is located (if any)
*
* @param excelFileLocation
*/
public void setExcelFileLocation(String excelFileLocation) {
this.excelFileLocation = excelFileLocation;
}
/**
* Set the rulebase that we will use
*
* @param Rulebase
* the excelRules to set
*/
public void setRuleBase(RuleBase excelRules) {
this.excelRules = excelRules;
}
/**
* @param serialFileLocation
* the serialFileLocation to set
*/
public void setSerialFileLocation(String serialFileLocation) {
this.serialFileLocation = serialFileLocation;
}
/**
* @param sharedWorkingMemory the sharedWorkingMemory to set
*/
public void setSharedWorkingMemory(StatefulSession sharedWorkingMemory) {
this.statefulSession = sharedWorkingMemory;
}
/**
* @return the sharedWorkingMemory
*/
public StatefulSession getSharedWorkingMemory() {
return statefulSession;
}
}