Search code examples
pluginsdroolsgrails3

How to use drools in grails3 without any plugin?


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.


Solution

  • 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:

    1. include minimal DROOLS dependency in build.gradle
    2. choose a folder to store files.drl
    3. have some entities to use as facts to process (not mandatory to persist them, so I skip hibernate configuration)
    4. implement a service to build rule base knowledge and to get a session
    5. 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.