How to use drools in grails3 without any plugin? - plugins

Is it possible to use drools rule engine in GRAILS3 without any plugin installation? I ask that because I know drools is implemented in java and actual official plugin by Ken Siprell for GRAILS is (apparently) no more working.

After much investigation and many attempts, I got a small GRAILS3 API service through which it is possible to process data using DROOLS engine without any plugin. All that is possible because DROOLS is based on java and because of perfect compatibility between GRAILS and Java.
All you need is the following:
include minimal DROOLS dependency in build.gradle
choose a folder to store files.drl
have some entities to use as facts to process (not mandatory to persist them, so I skip hibernate configuration)
implement a service to build rule base knowledge and to get a session
implement some methods into API controller to process data through DROOLS (having got its session from service)
Below there is a simple example of all that:
DROOLS dependencies (in build.gradle):
runtime "org.drools:drools-compiler:6.5.0.Final"
compile "org.drools:drools-core:6.5.0.Final"
compile "org.drools:knowledge-api:6.5.0.Final"
DRL storage in src/rules (reference to this path will be in the service, see below): myrules.drl
import my.entities.Book;
import java.util.List;
rule "Find author"
salience 10
when
$book: Book( author=="Shakespeare" )
then
System.out.println("Book found, date:"+$book.getDate0());
end
Some entity, for example Book:
package my.entities
import java.util.Date
class Book {
String title, author
Date date0
}
Service to build DROOLS knowledge and get session (I prepared a stateless engine, lighter than the stateful one):
package my.services
import grails.converters.*
import org.kie.api.runtime.*;
import org.kie.internal.io.ResourceFactory;
import org.kie.api.*;
import org.kie.api.io.*;
import org.kie.api.builder.*;
class DroolsService {
def getSession() {
def path = "src/rules"
def lru = ["myrules.drl"]
def rules = []
lru.each{
rules.add("${path}${it}")
}
StatelessKieSession ksess = buildSession(rules)
return ksess
}
}
private buildSession(def lfile) {
println "Building DROOLS session..."
try {
def lres = []
lfile.each{
Resource resource = ResourceFactory.newFileResource(new File(it));
lres.add(resource)
}
KieContainer kieContainer = buildKieContainer(lres)
StatelessKieSession kieSession = kieContainer.newStatelessKieSession()
return kieSession
} catch(Exception e) {
e.printStackTrace()
return null
}
protected KieContainer buildKieContainer(def lres) {
KieServices kieServices = KieServices.Factory.get()
KieFileSystem kieFileSystem = kieServices.newKieFileSystem()
lres.each{
kieFileSystem.write("src/main/resources/rule.drl", it)
}
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll()
Results results = kieBuilder.results
if (results.hasMessages(Message.Level.ERROR)) {
throw new IllegalStateException(this.class.name + ": " + results.messages.toString())
}
KieContainer kieContainer = kieServices.newKieContainer(kieServices.repository.defaultReleaseId)
kieContainer
}
}
And use of service in API controller:
class ApiController {
def droolsService
def proc = {
def sess = droolsService.getSession()
def mess = "ok DROOLS proc from JSON"
Book book = null
if (params.contbook) {
book = new Book(JSON.parse(params.contbook))
sess.execute book
}
response.status = 200
render mess
}
In the controller I take json data from parameter and populate an entity by them, in order to execute it with the rule engine initialised by the DROOLS service. Of course this is a very simple solution, but it is working.
Some notes:
the RHS part of each drl rule (after "then") must be java, so, as in the example, you cannot access directly private property of the entity but you have to use a getter or setter (implicitly created by GRAILS)
getSession create a new build of the knowledge and a new session and that is not optimal, you may redesign it in order to store a DROOLS session and then reuse it through a user session
you can have several files.drl in the same folder and then you have to list them all in the service in order to pack them in the knowledge engine
Hope all that is useful.

Related

Adding rule file when configuring kie module programmatically in drools

I'm new to Drools and trying to create a Drools based java app.
I have decided to create a Kie Module programmatically instead of writing an XML file. In order to do that, I have below configurations
#Configuration
public class KieContainerConfig {
private static String drlFile = "com/rules/eligibility.drl";
#Bean
public StatelessKieSession kieSession() {
KieServices kieServices = KieServices.Factory.get();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
KieBaseModel kieBaseModel1 = kieModuleModel.newKieBaseModel("KBase1 ")
.setDefault(true)
.setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
.setEventProcessingMode(EventProcessingOption.STREAM)
.addPackage(drlFile); // adding drl file
kieBaseModel1.newKieSessionModel("KSession1")
.setDefault(true)
.setType(KieSessionModel.KieSessionType.STATELESS)
.setClockType(ClockTypeOption.get("realtime"));
KieFileSystem kfs = kieServices.newKieFileSystem();
kfs.writeKModuleXML(kieModuleModel.toXML());
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
List<Message> messages = kieBuilder.getResults().getMessages(Message.Level.ERROR);
if (!messages.isEmpty()) {
for (Message err : messages) {
System.err.println(err);
}
}
KieContainer kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
return kieContainer.newStatelessKieSession("KSession1");}
but when I run the code, app emit java.lang.RuntimeException: Unexpected globa error. But when I change the code by removing .addPackage(drlFile); and adding kfs.write(ResourceFactory.newClassPathResource(drlFile)); to the KieFileSystem, Code runs and provide the expected results.
Why I cannot mention the package when creating Kie Base Model instead of using Kie File System's write method?
Per the Javadoc for KieBaseModel, the addPackage method takes a pattern. Most commonly it's used like this:
KieBaseModel model = ...
.addPackage("*");
But if you have your rules organized into packages, you could add a package into the model like this:
KieBaseModel model = ...
.addPackage("org.mycompany.rules");
Your alternative, using classpath resources, is how you'd go about adding individual files.
(I recommend using the XML to configure your system. I too have built Spring and Spring-Boot apps with drools, and have never seen the need to over-complicate my life by doing these things manually that Drools does for you from a config file.)

Why I always got the Unexpected global error from drools?

When I fire the rules I got a strange error.
The error details are
java.lang.RuntimeException: Unexpected global [validateResult]
at org.drools.core.impl.StatefulKnowledgeSessionImpl.setGlobal(StatefulKnowledgeSessionImpl.java:1209)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.validate(SignUpProjectServiceImpl.java:190)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.validate(SignUpProjectServiceImpl.java:204)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.signUp(SignUpProjectServiceImpl.java:102)
at com.hikedu.backend.controller.ProjectApplicationRecordController.signUp(ProjectApplicationRecordController.java:94)
at com.hikedu.backend.controller.ProjectApplicationRecordController$$FastClassBySpringCGLIB$$dc339407.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
Here is my code to fire the rules
KieSession session = sessionBuilder.build(theDsl);
ProjectVersion latestVersion = projectVersionService.getLatestVersionIfNotExistsThenThrowException(projectId);
User user = userService.getUserIfNotExistsThenThrowException(userId);
ApplicationInfo info = getTheInsertObj(userId, projectId);
ProjectSignUpValidateResultDTO resultDTO = new ProjectSignUpValidateResultDTO();
resultDTO.setPass(true);
session.setGlobal("validateResult", resultDTO);
session.insert(latestVersion);
session.insert(info);
session.insert(user);
session.fireAllRules(1);
session.dispose();
return resultDTO;
I searched a lot about this error. The answers all talking the same thing--The dsl file must declar the global and the declar name and path must be euqal to the code given
But I confirmd again and again my dsl and my code there is not found any mistaken.
I tried to change the global name to nother one but still get that error.
So please help me.
Here is my dsl
import com.hikedu.backend.model.User;
import com.hikedu.backend.model.ProjectVersion;
import java.util.Map;
import com.hikedu.backend.dto.signupproject.ApplicationInfo
import java.util.Date
import java.sql.Timestamp
global com.hikedu.backend.dto.project.ProjectSignUpValidateResultDTO validateResult
rule "department not match"
no-loop
when
$p : ProjectVersion()
$u : User($p.applicationRequirements.departmentId not contains departmentOfJoined.id)
then
validateResult.setPass(false);
validateResult.setTheReasonOfUnPass("some reason");
end
And I did the debug to check the globals of the session. Here is the debug result
The drools version I am using is
Here is the KieSessionBuilder.build method
#Override
public KieSession build(String dsl) {
if (dsl == null) {
throw new RuntimeException("Dsl cannot be null");
}
KieHelper helper = new KieHelper();
helper.setClassLoader(getClass().getClassLoader());
helper.addContent(dsl, ResourceType.DSL);
KieBase base = helper.build();
return base.newKieSession();
}
Thanks you all. My english dost not good well please forgive me.
Coming late to the party, but I had the same error, but for a different reason. I was changing a system using Drools 7.0.12 from using a stateless session to a stateful session. It would appear that in a stateful session Drools is checking that the global is actually defined in at least one .drl file. If there is no "global" definition in a .drl file then the unexpected Global error is thrown. In a stateless session, no such check is made.
I resovled the error by myself.
Here is the solution:
Change the way of build drl.
#Override
public KieSession build(String drl) {
if (drl == null) {
throw new RuntimeException("Drl cannot be null");
}
kieFileSystem.write("src/main/resources/" + drl.hashCode() + ".drl", kieServices.getResources().newReaderResource(new StringReader(drl)));
KieBuilder builder = kieServices.newKieBuilder(kieFileSystem).buildAll();
Results results = builder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
throw new IllegalStateException("##errors : " + results.getMessages());
}
KieContainer container = kieServices.newKieContainer(builder.getKieModule().getReleaseId());
return container.newKieSession();
}
After I changed the way of build drl got another error :
java.lang.RuntimeException: Illegal class for global. Expected [com.hikedu.backend.dto.project.ProjectSignUpValidateResultDTO], found [com.hikedu.backend.dto.project.ProjectSignUpValidateResultDTO].
at org.drools.core.impl.StatefulKnowledgeSessionImpl.setGlobal(StatefulKnowledgeSessionImpl.java:1211)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.validate(SignUpProjectServiceImpl.java:190)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.validate(SignUpProjectServiceImpl.java:204)
at com.hikedu.backend.service.impl.signupproject.SignUpProjectServiceImpl.signUp(SignUpProjectServiceImpl.java:102)
at com.hikedu.backend.controller.ProjectApplicationRecordController.signUp(ProjectApplicationRecordController.java:94)
at com.hikedu.backend.controller.ProjectApplicationRecordController$$FastClassBySpringCGLIB$$dc339407.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
I searched a lot about this new error. And finally got the solution.
This error because I am using the devtools in my project. And the devtools will use itself classloader to load all class. But the drools load the class by another classloader.
Here is the debug info :
The type is load by drools.
The value is load by devtools
How to resolve this ?
Just add the META-INF/spring-devtools.properties file. The content is
restart.include.drools=/drools-[\\s\\S]+\.jar
restart.include.kie=/kie-[\\s\\S]+\.jar
This will make sure the drools and kie load by devtools itself class loader.
And then the error fixed.
Here is some document
https://github.com/spring-projects/spring-boot/issues/3316
https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html

Drools Creating Rules(DRL) Programatically not working in drools-distribution-6.5.0.Final

I am working on putting some business rules in drool engine.We cannot use KIE workspace UI to author rules.So that is out.
Problem Statement:Create a application(front end angular UI) back end spring boot microservice to author rules.Those authored rules needs to be dynamically refreshed without having to restart the jvm and other micro services which want to use these rules,should use them.For e.g:granting credit or interest rates based dealer credit history ,duration with bank and any new rules which might be designed as per author.I started looking on this and theoretically one could build something like this by using API of drools compiler library.
There is code example here.
for real time refreshing,there is something called KnowledgeAgent.
https://docs.jboss.org/drools/release/5.2.0.Final/drools-guvnor-docs/html/ch09.html
What is the new accepted way of programmatically creating new drools rules in Drools 6?
My problem is I am not able to make this work.Code is running fine but I am not able to see the drl file getting written.In debug mode,I can see string object with proper drl structure.Has anyone encountered this problem before.?
I have seen some examples on github where people have done yoman job to integrate drools in spring boot.I can start with building my service,but I need to be sure that this something which is possible to do
Following code will help you create drool rule using code.It is not recommended way and most of people use kie-web interface to design and modify drool rules.Not sure about how we can modify already created .drl files.But this has given me start.Going
package com.sample.model;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import org.drools.compiler.lang.DrlDumper;
import org.drools.compiler.lang.api.DescrFactory;
import org.drools.compiler.lang.api.PackageDescrBuilder;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
//#SuppressWarnings("restriction")
public class GenerateRule {
public static void main(String[] args) {
// TODO Auto-generated method stub
KieContainer container=build(KieServices.Factory.get());
System.out.println(container.getReleaseId());
System.out.println(container.getKieBase());
}
public static KieContainer build(KieServices kieServices){
KieFileSystem fileSystem=kieServices.newKieFileSystem();
ReleaseId releaseId=kieServices.newReleaseId("com.example.rulesengine",
"model-test", "1.0-SNAPSHOT");
fileSystem.generateAndWritePomXML(releaseId);
//fileSystem.write("D:/workspace/DroolSamples/src/main/resources/rules/rules.drl", getResource(kieServices, "D:/workspace/DroolSamples/src/main/resources/rules/rules.drl"));
addRule(fileSystem);
KieBuilder kieBuilder = kieServices.newKieBuilder(fileSystem);
kieBuilder.buildAll();
if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" +
kieBuilder.getResults().toString());
}
return kieServices.newKieContainer(releaseId);
}
#SuppressWarnings("restriction")
private static void addRule(KieFileSystem kieFileSystem) {
PackageDescrBuilder packageDescrBuilder = DescrFactory.newPackage();
packageDescrBuilder
.name("com.sample.model")
.newRule()
.name("Is of valid age")
.lhs()
.pattern("Person").constraint("age < 18")
.id("$a", true).end()
//.pattern().id("$a", false).end()
.end()
.rhs("$a.setShowBanner( false );")
//.rhs("insert(new Person())")
.end();
String rules = new DrlDumper().dump(packageDescrBuilder.getDescr());
KieFileSystem fileSystem=kieFileSystem.write("D:/newrule.drl", rules);
try{
// create new file
File file = new File("src/main/resources/rules/test.drl");
file.createNewFile();
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(rules);
// close connection
bw.close();
System.out.println("File Created Successfully");
}catch(Exception e){
System.out.println(e);
}
}
private static Resource getResource(KieServices kieServices, String resourcePath) {
try {
// InputStream is = com.google.common.io.Resources.getResource(resourcePath).openStream(); //guava
InputStream is=new FileInputStream(new File(resourcePath));
return kieServices.getResources()
.newInputStreamResource(is)
.setResourceType(ResourceType.DRL);
} catch (IOException e) {
throw new RuntimeException("Failed to load drools resource file.", e);
}
}
}

PackageBuilder.java not available in Drools 6.1.0.final

I am new to drools and we are currently using Drools 5.4.0 in our project.
Currently we are using RuleCompiler.java and PackageBuilder.java classes of Drools 5.4.0 to compile the .xls files and create ruleSetObject. The code snippet is as given below
String drlFromFile = null;
if (Pattern.matches(regexPattern, file.getName())) {
if (file.getName().contains("csv") || file.getName().contains("CSV")) {
drlFromFile = RuleCompiler.compileCSV(file);
} else {
drlFromFile = RuleCompiler.compileSpreadSheet(file);
}
if (drlFromFile == null || drlFromFile.isEmpty()) {
logger.debug("Unable to Compile Rule Sheet: " + file.getName());
throw new DroolsParserException("Unable to Compile Rule Sheet: " + file.getName());
}
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl(new StringReader(drlFromFile));
Package ruleSetObject = builder.getPackage();
// Registering the compiled drl object in ruleExecutionSetRegistry
ruleExecutionSetRegistry.registerRuleSetObject(file.getName(), ruleSetObject,
getRuleEngineProviderName());
}
Now we need to upgrade to Drools 6.1.0.final, but I can not find the PackageBuilder.java class there. I tried to search for its replacement but didn't get anything.
Is any new class has been introduced in place of PackageBuilder.java? Does any one guide me how to use that class?
The 'new' way to do things is by defining a KieModule. Essentially, you create a Maven project which wraps your Drools rules, and then add that project as a dependency for your runtime. Generally, this expects that you will follow certain conventions in how you structure your project so that Drools can find your rules itself.
However, you may (like me) find it easier to migrate without completely restructuring your existing project. To achieve this, you can still build up a KieService (the new KnowledgeBase), by adding files to a KieFileSystem. Here's a rough example of doing that:
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kfs = kieServices.newKieFileSystem();
kfs.write(ResourceFactory.newFileResource(resource.getPath()));
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
if (kieBuilder.getResults().hasMessages(Level.ERROR)) {
// It didn't build. Do something about it...
}
KieContainer kieContainer = kieServices
.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
KieSession kieSession = kieContainer.newKieSession();
And you're pretty much ready to go. In case it's useful, a recent demo project of mine contains an example of doing this for plain .drl files, and from what I understand, it should be pretty much the same if you want to add a spreadsheet to the KieFileSystem instead of DRL.

Custom event listener example in Grails documentation

I'm trying to add a custom GORM event listener class in Bootstrap.groovy, as described in the Grails documentation but its not working for me. Here is the code straight from the docs:
def init = {
application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
applicationContext.addApplicationListener new MyPersistenceListener(datastore)
}
}
When I run it, the compiler complains that application and applicationContext are null. I've tried adding them as class level members but they don't get magically wired up service-style. The closest I've got so far is:
def grailsApplication
def init = { servletContext ->
def applicationContext = servletContext.getAttribute(ApplicationAttributes.APPLICATION_CONTEXT)
grailsApplication.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
applicationContext.addApplicationListener new GormEventListener(datastore)
}
}
But I still get errors: java.lang.NullPointerException: Cannot get property 'datastores' on null object.
Thanks for reading...
EDIT: version 2.2.1
If you do:
ctx.getBeansOfType(Datastore).values().each { Datastore d ->
ctx.addApplicationListener new MyPersistenceListener(d)
}
This should work without needing the Hibernate plugin installed
That looks like it should work, although I'd do it a bit differently. BootStrap.groovy does support dependency injection, so you can inject the grailsApplication bean, but you can also inject eventTriggeringInterceptor directly:
class BootStrap {
def grailsApplication
def eventTriggeringInterceptor
def init = { servletContext ->
def ctx = grailsApplication.mainContext
eventTriggeringInterceptor.datastores.values().each { datastore ->
ctx.addApplicationListener new MyPersistenceListener(datastore)
}
}
}
Here I still inject grailsApplication but only because I need access to the ApplicationContext to register listeners. Here's my listener (simpler than what the docs claim the simplest implementation would be btw ;)
import org.grails.datastore.mapping.core.Datastore
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
class MyPersistenceListener extends AbstractPersistenceEventListener {
MyPersistenceListener(Datastore datastore) {
super(datastore)
}
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
println "Event $event.eventType $event.entityObject"
}
boolean supportsEventType(Class eventType) { true }
}
Finally stumbled onto a working Bootstrap.groovy, thanks to this post but I don't think its the best way to do it, rather its a work around.
def init = { servletContext ->
def applicationContext = servletContext.getAttribute(ApplicationAttributes.APPLICATION_CONTEXT)
applicationContext.addApplicationListener new GormEventListener(applicationContext.mongoDatastore)
}
So basically I'm hard-coding the MongoDB datastore directly as opposed to iterating over the available ones, as the docs suggest.
To save you reading the comments to the first answer, the adapted version I provided in the Question (as well as Burt's answer) only works if the Hibernate plugin is installed but in my case I was using the MongoDB plugin so had no need for the Hibernate plugin (it in fact broke my app in other ways).