/*
* Copyright 2006 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.contrib;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.RuntimeDroolsException;
import org.drools.builder.DecisionTableConfiguration;
import org.drools.builder.DecisionTableInputType;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.compiler.DrlParser;
import org.drools.compiler.DroolsParserException;
import org.drools.compiler.PackageBuilder;
import org.drools.compiler.PackageBuilderConfiguration;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.drools.definition.KnowledgePackage;
import org.drools.definition.rule.Rule;
import org.drools.ide.common.client.modeldriven.brl.RuleModel;
import org.drools.ide.common.server.util.BRDRLPersistence;
import org.drools.ide.common.server.util.BRXMLPersistence;
import org.drools.io.ResourceFactory;
import org.drools.lang.Expander;
import org.drools.lang.dsl.DSLMappingFile;
import org.drools.lang.dsl.DSLTokenizedMappingFile;
import org.drools.lang.dsl.DefaultExpander;
import org.drools.lang.dsl.DefaultExpanderResolver;
import org.drools.rule.Package;
import org.drools.core.util.DroolsStreamUtils;
/**
* An ant task to allow rulebase compilation and serialization during a build.
*
* @author etirelli
*/
public class DroolsCompilerAntTask extends MatchingTask {
public static String BRLFILEEXTENSION = ".brl";
public static String XMLFILEEXTENSION = ".xml";
public static String RULEFLOWMODELFILEEXTENSION = ".rfm";
public static String RULEFLOWFILEEXTENSION = ".rf";
public static String DSLFILEEXTENSION = ".dsl";
public static String DSLRFILEEXTENSION = ".dslr";
public static String XLSFILEEXTENSION = ".xls";
public static String DROOLSPACKAGEEXTENSION = ".package";
public static String PACKAGEBINFORMAT = "package";
public static String PACKAGEBINTYPE = "knowledge";
private File srcdir;
private File toFile;
private Path classpath;
private String binformat;
private String bintype;
private String verbose;
private boolean verboseoption;
/**
* Source directory to read DRL files from
*
* @param directory
*/
public void setSrcDir(File directory) {
this.srcdir = directory;
}
/**
* File to serialize the rulebase to
*
* @param toFile
*/
public void setToFile(File toFile) {
this.toFile = toFile;
}
/**
* The classpath to use when compiling the rulebase
*
* @param classpath
*/
public void setClasspath(Path classpath) {
createClasspath().append(classpath);
}
/**
* Classpath to use, by reference, when compiling the rulebase
*
* @param a
* reference to an existing classpath
*/
public void setClasspathref(Reference r) {
createClasspath().setRefid(r);
}
/**
* Adds a path to the classpath.
*
* @return created classpath
*/
public Path createClasspath() {
if (this.classpath == null) {
this.classpath = new Path(getProject());
}
return this.classpath.createPath();
}
/**
* Task's main method
*/
public void execute() throws BuildException {
super.execute();
verboseoption = verbose != null && verbose.equalsIgnoreCase("true");
// checking parameters are set
if (toFile == null) {
throw new BuildException(
"Destination rulebase file does not specified.");
}
// checking parameters are set
if (srcdir == null) {
throw new BuildException("Source directory not specified.");
}
if (!srcdir.exists()) {
throw new BuildException("Source directory does not exists."
+ srcdir.getAbsolutePath());
}
AntClassLoader loader = null;
try {
// create a specialized classloader
loader = getClassLoader();
if (PACKAGEBINTYPE.equals(bintype)) {
createWithKnowledgeBuilder(loader);
} else {
createWithPackageBuilder(loader);
}
} catch (Exception e) {
throw new BuildException("RuleBaseTask failed: " + e.getMessage(),
e);
} finally {
if (loader != null) {
loader.resetThreadContextLoader();
}
}
}
private void createWithKnowledgeBuilder(AntClassLoader loader)
throws FileNotFoundException, DroolsParserException, IOException {
// create a package builder configured to use the given classloader
KnowledgeBuilder kbuilder = getKnowledgeBuilder(loader);
// add files to rulebase
compileAndAddFiles(kbuilder);
// gets the packages
Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
if(verboseoption) {
Iterator<KnowledgePackage> iter = pkgs.iterator();
while(iter.hasNext()) {
KnowledgePackage pkg = iter.next();
log("** Content of package: " + pkg.getName());
Iterator<Rule> riter = pkg.getRules().iterator();
while(riter.hasNext()) {
log("\tRule name: " + riter.next().getName());
}
}
}
// creates the knowledge base
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
// adds the packages
kbase.addKnowledgePackages(pkgs);
if (PACKAGEBINFORMAT.equals(binformat)) {
Iterator<KnowledgePackage> iter = pkgs.iterator();
while(iter.hasNext()) {
KnowledgePackage pkg = iter.next();
if(verboseoption) {
log("** Serializing package ["+pkg.getName()+"] to destination file. **** THIS WILL OVERRIDE ANY PREVIOUSLY SERIALIZED PACKAGE ****");
}
serializeObject(pkg);
}
} else {
if(verboseoption) {
log("** Serializing KnowledgeBase to destination file.");
}
// serialize the knowledge base to the destination file
serializeObject(kbase);
}
}
private void compileAndAddFiles(KnowledgeBuilder kbuilder)
throws FileNotFoundException, DroolsParserException, IOException {
// get the list of drools package files
String[] droolsPackageNames = getDroolsPackageFileList();
if (droolsPackageNames != null) {
for (int i = 0; i < droolsPackageNames.length; i++) {
// compile rule file and add to the builder
compileAndAddFile(kbuilder, droolsPackageNames[i]);
}
}
// get the list of files to be added to the rulebase
String[] fileNames = getFileList();
for (int i = 0; i < fileNames.length; i++) {
// compile rule file and add to the builder
compileAndAddFile(kbuilder, fileNames[i]);
}
if (kbuilder.hasErrors()) {
System.err.println(kbuilder.getErrors().toString());
}
}
private void compileAndAddFiles(PackageBuilder pbuilder)
throws FileNotFoundException, DroolsParserException, IOException {
// get the list of drools package files
String[] droolsPackageNames = getDroolsPackageFileList();
if (droolsPackageNames != null) {
for (int i = 0; i < droolsPackageNames.length; i++) {
// compile rule file and add to the builder
compileAndAddFile(pbuilder, droolsPackageNames[i]);
}
}
// get the list of files to be added to the rulebase
String[] fileNames = getFileList();
for (int i = 0; i < fileNames.length; i++) {
// compile rule file and add to the builder
compileAndAddFile(pbuilder, fileNames[i]);
}
if (pbuilder.hasErrors()) {
System.err.println(pbuilder.getErrors().toString());
}
}
private void createWithPackageBuilder(AntClassLoader loader)
throws FileNotFoundException, DroolsParserException, IOException {
// create a package builder configured to use the given classloader
PackageBuilder builder = getPackageBuilder(loader);
compileAndAddFiles(builder);
org.drools.rule.Package[] packages = builder.getPackages();
if(verboseoption) {
for(org.drools.rule.Package pkg : packages) {
log("** Content of package: " + pkg.getName());
Rule[] rules = pkg.getRules();
for(Rule rule : rules) {
log("\tRule name: " + rule.getName());
}
}
}
// gets the package
//org.drools.rule.Package pkg = builder.getPackage();
// creates the rulebase
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
// adds the packages
ruleBase.addPackages(packages);
if (PACKAGEBINFORMAT.equals(binformat)) {
for(org.drools.rule.Package pkg : packages) {
if(verboseoption) {
log("** Serializing package ["+pkg.getName()+"] to destination file. **** THIS WILL OVERRIDE ANY PREVIOUSLY SERIALIZED PACKAGE ****");
}
serializeObject(pkg);
}
} else {
if(verboseoption) {
log("** Serializing RuleBase to destination file.");
}
// serialize the rule base to the destination file
serializeObject(ruleBase);
}
}
/**
* @param ruleBase
* @throws FileNotFoundException
* @throws IOException
*/
private void serializeObject(Object object) throws FileNotFoundException,
IOException {
FileOutputStream fout = null;
try {
fout = new FileOutputStream(toFile);
DroolsStreamUtils.streamOut(fout, object);
} finally {
if (fout != null) {
fout.close();
}
}
}
/**
* @param builder
* @param fileName
* @return
* @throws FileNotFoundException
* @throws DroolsParserException
* @throws IOException
*/
private void compileAndAddFile(KnowledgeBuilder kbuilder, String fileName)
throws FileNotFoundException, DroolsParserException, IOException {
FileReader fileReader = new FileReader(new File(this.srcdir, fileName));
if (fileName.endsWith(DroolsCompilerAntTask.BRLFILEEXTENSION)) {
// TODO: Right now I have to first change this to String. Change to
// use KnowledgeBuilder directly when the support for that is done.
// -Toni Rikkola-
RuleModel model = BRXMLPersistence.getInstance().unmarshal(
loadResource(fileName));
String packagefile = loadResource(resolvePackageFile(this.srcdir
.getAbsolutePath()));
model.name = fileName.replace(
DroolsCompilerAntTask.BRLFILEEXTENSION, "");
ByteArrayInputStream istream = new ByteArrayInputStream(
(packagefile + BRDRLPersistence.getInstance()
.marshal(model)).getBytes());
Reader instream = new InputStreamReader(istream);
kbuilder.add(ResourceFactory.newReaderResource(instream),
ResourceType.DRL);
} else if (fileName
.endsWith(DroolsCompilerAntTask.RULEFLOWMODELFILEEXTENSION)
|| fileName
.endsWith(DroolsCompilerAntTask.RULEFLOWFILEEXTENSION)) {
kbuilder.add(ResourceFactory.newReaderResource(fileReader),
ResourceType.DRF);
} else if (fileName.endsWith(DroolsCompilerAntTask.XMLFILEEXTENSION)) {
kbuilder.add(ResourceFactory.newReaderResource(fileReader),
ResourceType.XDRL);
} else if (fileName.endsWith(DroolsCompilerAntTask.XLSFILEEXTENSION)) {
DecisionTableConfiguration dtableconfiguration = KnowledgeBuilderFactory
.newDecisionTableConfiguration();
dtableconfiguration.setInputType(DecisionTableInputType.XLS);
kbuilder.add(ResourceFactory.newReaderResource(fileReader),
ResourceType.DTABLE, dtableconfiguration);
// } else if
// (fileName.endsWith(DroolsCompilerAntTask.DSLFILEEXTENSION)) {
//
// kbuilder.add(ResourceFactory.newReaderResource(fileReader),
// ResourceType.DSL);
} else if (fileName.endsWith(DroolsCompilerAntTask.DSLRFILEEXTENSION)) {
// Get the DSL too.
String[] dsls = resolveDSLFilesToArray();
for (int i = 0; i < dsls.length; i++) {
kbuilder.add(ResourceFactory.newFileResource(new File(
this.srcdir, dsls[i])), ResourceType.DSL);
}
kbuilder.add(ResourceFactory.newReaderResource(fileReader),
ResourceType.DSLR);
} else {
kbuilder.add(ResourceFactory.newReaderResource(fileReader),
ResourceType.DRL);
}
}
/**
* @param builder
* @param fileName
* @return
* @throws FileNotFoundException
* @throws DroolsParserException
* @throws IOException
*/
private void compileAndAddFile(PackageBuilder builder, String fileName)
throws FileNotFoundException, DroolsParserException, IOException {
InputStreamReader instream = null;
File file = new File(this.srcdir, fileName);
try {
if (fileName.endsWith(DroolsCompilerAntTask.BRLFILEEXTENSION)) {
RuleModel model = BRXMLPersistence.getInstance().unmarshal(
loadResource(fileName));
String packagefile = loadResource(resolvePackageFile(this.srcdir
.getAbsolutePath()));
model.name = fileName.replace(
DroolsCompilerAntTask.BRLFILEEXTENSION, "");
ByteArrayInputStream istream = new ByteArrayInputStream(
(packagefile + BRDRLPersistence.getInstance().marshal(
model)).getBytes());
instream = new InputStreamReader(istream);
} else {
instream = new InputStreamReader(new FileInputStream(file));
}
if (fileName
.endsWith(DroolsCompilerAntTask.RULEFLOWMODELFILEEXTENSION)
|| fileName
.endsWith(DroolsCompilerAntTask.RULEFLOWFILEEXTENSION)) {
builder.addRuleFlow(instream);
} else if (fileName
.endsWith(DroolsCompilerAntTask.XMLFILEEXTENSION)) {
builder.addPackageFromXml(instream);
} else if (fileName
.endsWith(DroolsCompilerAntTask.XLSFILEEXTENSION)) {
final SpreadsheetCompiler converter = new SpreadsheetCompiler();
final String drl = converter.compile(new FileInputStream(file),
InputType.XLS);
System.out.println(drl);
builder.addPackageFromDrl(new StringReader(drl));
} else if (fileName
.endsWith(DroolsCompilerAntTask.DSLRFILEEXTENSION)) {
DrlParser parser = new DrlParser();
String expandedDRL = parser.getExpandedDRL(
loadResource(fileName), resolveDSLFiles());
builder.addPackageFromDrl(new StringReader(expandedDRL));
} else {
builder.addPackageFromDrl(instream);
}
} finally {
if (instream != null) {
instream.close();
}
}
}
private String[] resolveDSLFilesToArray() {
Collection<String> list = new ArrayList<String>();
final File dir = new File(this.srcdir.getAbsolutePath());
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".dsl");
}
};
return dir.list(filter);
}
private DefaultExpanderResolver resolveDSLFiles() throws IOException {
DefaultExpanderResolver resolver = new DefaultExpanderResolver();
final File dir = new File(this.srcdir.getAbsolutePath());
DSLMappingFile file = new DSLTokenizedMappingFile();
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".dsl");
}
};
String[] children = dir.list(filter);
if (children.length == 0) {
throw new BuildException(
"There are no DSL files for this directory:"
+ this.srcdir.getAbsolutePath());
}
for (int index = 0; index < children.length; index++) {
if (file.parseAndLoad(new StringReader(
loadResource(children[index])))) {
final Expander expander = new DefaultExpander();
expander.addDSLMapping(file.getMapping());
resolver.addExpander("*", expander);
} else {
throw new RuntimeDroolsException(
"Error parsing and loading DSL file."
+ file.getErrors());
}
}
return resolver;
}
private String resolvePackageFile(String dirname) {
File dir = new File(dirname);
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".package");
}
};
String[] children = dir.list(filter);
if (children.length > 1) {
throw new BuildException(
"There are more than one package configuration file for this directory :"
+ dirname);
}
if (children.length == 0) {
throw new BuildException(
"There is no package configuration file for this directory:"
+ dirname);
}
return children[0];
}
private String loadResource(final String name) throws IOException {
final InputStream in = new FileInputStream(this.srcdir + "/" + name);
final Reader reader = new InputStreamReader(in);
final StringBuffer text = new StringBuffer();
final char[] buf = new char[1024];
int len = 0;
while ((len = reader.read(buf)) >= 0) {
text.append(buf, 0, len);
}
return text.toString();
}
/**
* @return
*/
private AntClassLoader getClassLoader() {
// defining a new specialized classloader and setting it as the thread
// context classloader
AntClassLoader loader = null;
if (classpath != null) {
loader = new AntClassLoader(PackageBuilder.class.getClassLoader(),
getProject(), classpath, false);
} else {
loader = new AntClassLoader(PackageBuilder.class.getClassLoader(),
false);
}
loader.setThreadContextLoader();
return loader;
}
/**
* @param loader
* @return
*/
private PackageBuilder getPackageBuilder(AntClassLoader loader) {
// creating package builder configured with the give classloader
PackageBuilderConfiguration conf = new PackageBuilderConfiguration(
loader);
PackageBuilder builder = new PackageBuilder(conf);
return builder;
}
/**
* Returns the list of files to be added into the rulebase
*
* @return
*/
private String[] getFileList() {
// scan source directory for rule files
DirectoryScanner directoryScanner = getDirectoryScanner(srcdir);
String[] excludes = { "*\\*\\*." + DROOLSPACKAGEEXTENSION };
directoryScanner.setExcludes(excludes);
String[] fileNames = directoryScanner.getIncludedFiles();
if (fileNames == null || fileNames.length <= 0) {
throw new BuildException(
"No rule files found in include directory.");
}
return fileNames;
}
/**
* Returns the list of drools package files.
*
* @return drools package files
*/
private String[] getDroolsPackageFileList() {
DirectoryScanner directoryScanner = getDirectoryScanner(srcdir);
String[] includes = { "*\\*\\*." + DROOLSPACKAGEEXTENSION };
directoryScanner.setIncludes(includes);
String[] fileNames = directoryScanner.getIncludedFiles();
if (fileNames == null || fileNames.length <= 0) {
log("no drools package files found in " + srcdir);
}
return fileNames;
}
private KnowledgeBuilder getKnowledgeBuilder(AntClassLoader loader) {
// creating package builder configured with the give classloader
PackageBuilderConfiguration conf = new PackageBuilderConfiguration(
loader);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
.newKnowledgeBuilder(conf);
return kbuilder;
}
public void setBinformat(String binformat) {
this.binformat = binformat;
}
public String getBinformat() {
return binformat;
}
public String getBintype() {
return bintype;
}
public void setBintype(String bintype) {
this.bintype = bintype;
}
public void setVerbose(String verbose) {
this.verbose = verbose;
}
public String getVerbose() {
return verbose;
}
}