/******************************************************************************
* Copyright (c) 2010 Ansgar Konermann *
* *
* 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 de.lightful.testflux.drools;
import com.google.inject.Inject;
import de.lightful.testflux.drools.impl.IterableAdapter;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.drools.KnowledgeBase;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderError;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DroolsRuleTestListener implements ITestListener {
private static Logger log = Logger.getLogger(DroolsRuleTestListener.class);
private ThreadLocal<ITestResult> testResult = new ThreadLocal<ITestResult>();
private static final String TESTFLUX_CONFIG_FILE = "/testflux.properties";
public DroolsRuleTestListener() {
log.debug("Creating new instance of " + this.getClass().getName() + ".");
}
@Override
public void onTestStart(ITestResult result) {
testResult.set(result);
final Class<?> realTestClass = obtainJavaTestClass(result);
final Method realTestMethod = obtainJavaTestMethod(result);
KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
KnowledgeBase knowledgeBaseForClass = createNewKnowledgeBaseInstanceForClass(realTestClass, knowledgeBuilder);
KnowledgeBase knowledgeBaseForMethod = completeKnowledgeBaseForMethod(knowledgeBaseForClass, realTestMethod, knowledgeBaseForClass);
knowledgeBaseForMethod.addKnowledgePackages(knowledgeBuilder.getKnowledgePackages());
injectKnowledgeBase(realTestClass, result.getInstance(), knowledgeBaseForMethod, result);
}
private void injectKnowledgeBase(Class<?> realTestClass, Object testInstance, KnowledgeBase knowledgeBaseForMethod, ITestResult result) {
List<Field> matchingFields = findFieldsToInjectKnowledgeBaseInto(realTestClass);
for (Field matchingField : matchingFields) {
injectIntoField(matchingField, testInstance, knowledgeBaseForMethod, result);
}
}
private void injectIntoField(Field matchingField, Object testInstance, KnowledgeBase knowledgeBaseForMethod, ITestResult result) {
matchingField.setAccessible(true);
try {
matchingField.set(testInstance, knowledgeBaseForMethod);
}
catch (IllegalAccessException iae) {
throw new TestFluxException("Error injecting KnowledgeBase into " + result.getTestClass().getName() + "." + matchingField.getName());
}
}
private List<Field> findFieldsToInjectKnowledgeBaseInto(Class<?> realTestClass) {
final Field[] declaredFields = realTestClass.getDeclaredFields();
List<Field> matchingFields = new ArrayList<Field>();
for (Field declaredField : declaredFields) {
if (canAcceptKnowledgeBase(declaredField)) {
matchingFields.add(declaredField);
}
}
return matchingFields;
}
private boolean canAcceptKnowledgeBase(Field declaredField) {
if (declaredField.getAnnotation(NoInjection.class) != null) {
return false;
}
if ( declaredField.getAnnotation(Inject.class) == null ) {
return false;
}
final Class<?> fieldType = declaredField.getType();
if (fieldType.isAssignableFrom(KnowledgeBase.class)) {
return true;
}
return false;
}
private KnowledgeBase completeKnowledgeBaseForMethod(KnowledgeBase knowledgeBaseForClass, Method realTestMethod, KnowledgeBase baseForClass) {
return knowledgeBaseForClass;
}
private KnowledgeBase createNewKnowledgeBaseInstanceForClass(Class<?> realTestClass, KnowledgeBuilder knowledgeBuilder) {
final CompileRules compileRulesAnnotation = realTestClass.getAnnotation(CompileRules.class);
final RulesBaseDirectory rulesBaseDirectory = realTestClass.getAnnotation(RulesBaseDirectory.class);
KnowledgeBase knowledgeBaseForClass = knowledgeBuilder.newKnowledgeBase();
for (RuleSource ruleSource : compileRulesAnnotation.value()) {
addToKnowledgeBase(rulesBaseDirectory, ruleSource, knowledgeBuilder);
}
return knowledgeBaseForClass;
}
private void addToKnowledgeBase(RulesBaseDirectory rulesBaseDirectory, RuleSource ruleSource, KnowledgeBuilder knowledgeBuilder) {
boolean isForDirectory = isValueAvailable(ruleSource.directory());
boolean isForIndividualFile = isValueAvailable(ruleSource.file());
boolean conflict = isForDirectory && isForIndividualFile;
if (conflict) {
throw new TestFluxException("@" + RuleSource.class.getSimpleName() + " cannot be used to specify both directory '" + ruleSource.directory() + "'" +
" and file '" + ruleSource.file() + "' at the same time.");
}
if (isForDirectory) {
addAllFilesFromDirectory(knowledgeBuilder, ruleSource.directory(), rulesBaseDirectory);
}
else if (isForIndividualFile) {
addIndividualFileFromBaseDirectory(knowledgeBuilder, ruleSource.file(), rulesBaseDirectory);
}
}
private void addAllFilesFromDirectory(KnowledgeBuilder knowledgeBuilder, String directoryName, RulesBaseDirectory ruleBaseDirectory) {
String baseDirectory = determineBaseDirectory(ruleBaseDirectory);
File directory = fileFromBaseDirectory(directoryName, baseDirectory);
if (!directory.exists()) {
throw new TestFluxException("Directory " + directory.getAbsolutePath() + " given by @" + RuleSource.class.getSimpleName() + " must exist (but does not).");
}
if (!directory.isDirectory()) {
throw new TestFluxException("Directory " + directory.getAbsolutePath() + " given by @" + RuleSource.class.getSimpleName() + " must denote a directory (but does not).");
}
Iterator<File> fileIterator = null;
try {
fileIterator = FileUtils.iterateFiles(directory, new String[] {"drl"}, false);
}
catch (Throwable t) {
throw new TestFluxException("Caught " + t.getClass().getSimpleName() + ": " + t.getMessage());
}
for (File file : IterableAdapter.makeFrom(fileIterator)) {
addIndividualFile(knowledgeBuilder, file);
}
}
private String determineBaseDirectory(RulesBaseDirectory ruleBaseDirectory) {
String baseDirectory = null;
if (ruleBaseDirectory != null) {
baseDirectory = ruleBaseDirectory.value();
}
else {
baseDirectory = "src" + File.separator + "main" + File.separator + "rules";
}
final String rulesRootDirectory = testResult.get().getTestClass().getXmlTest().getSuite().getParameter("rulesRootDirectory");
return rulesRootDirectory + File.separator + baseDirectory;
}
private void addIndividualFileFromBaseDirectory(KnowledgeBuilder knowledgeBuilder, String filename, RulesBaseDirectory rulesBaseDirectory) {
String baseDirectory = determineBaseDirectory(rulesBaseDirectory);
File file = fileFromBaseDirectory(filename, baseDirectory);
addIndividualFile(knowledgeBuilder, file);
}
private void addIndividualFile(KnowledgeBuilder knowledgeBuilder, File file) {
ensureFileExists(file);
ensureIsRegularFile(file);
addFileToKnowledgeBase(knowledgeBuilder, file);
handleKnowledgeBuilderErrors(knowledgeBuilder);
}
private void handleKnowledgeBuilderErrors(KnowledgeBuilder knowledgeBuilder) {
if (knowledgeBuilder.hasErrors()) {
StringBuilder builder = new StringBuilder(1024);
builder.append("Drools " + knowledgeBuilder.getClass().getSimpleName() + " error occurred:");
for (KnowledgeBuilderError knowledgeBuilderError : knowledgeBuilder.getErrors()) {
builder.append("Error in line(s) [");
appendLinesTo(knowledgeBuilderError.getErrorLines(), builder);
builder.append("]:");
builder.append(knowledgeBuilderError.getMessage());
}
throw new TestFluxException(builder.toString());
}
}
private void ensureIsRegularFile(File file) {
if (!file.isFile()) {
throw new TestFluxException("File " + file.getAbsolutePath() + " given by @" + RuleSource.class.getSimpleName() + " must denote a regular file (but does not).");
}
}
private void ensureFileExists(File file) {
if (!file.exists()) {
throw new TestFluxException("File " + file.getAbsolutePath() + " not found, although given by @" + RuleSource.class.getSimpleName() + " annotation");
}
}
private File fileFromBaseDirectory(String filename, String baseDirectory) {
return new File(baseDirectory + File.separator + filename);
}
private void addFileToKnowledgeBase(KnowledgeBuilder knowledgeBuilder, File file) {
try {
knowledgeBuilder.add(ResourceFactory.newFileResource(file), ResourceType.DRL);
}
catch (Throwable t) {
throw new TestFluxException("Exception occurred while adding file " + file.getAbsolutePath() + " to knowledge base: " + t.getMessage());
}
}
private void appendLinesTo(int[] errorLines, StringBuilder appendToMe) {
final int numberOfLines = errorLines.length;
if (numberOfLines == 1) {
appendToMe.append(errorLines[0]);
return;
}
for (int i = 1; i < numberOfLines; i++) {
appendToMe.append(",");
appendToMe.append(errorLines[i]);
}
}
private boolean isValueAvailable(String fileOrDirectoryName) {
return (fileOrDirectoryName != null) && (!"".equals(fileOrDirectoryName));
}
private Class<?> obtainJavaTestClass(ITestResult result) {
return result.getTestClass().getRealClass();
}
private Method obtainJavaTestMethod(ITestResult result) {
return result.getMethod().getMethod();
}
@Override
public void onTestSuccess(ITestResult result) {
}
@Override
public void onTestFailure(ITestResult result) {
}
@Override
public void onTestSkipped(ITestResult result) {
}
@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
}
@Override
public void onStart(ITestContext context) {
}
@Override
public void onFinish(ITestContext context) {
}
}