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.)
Related
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
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.
I have set up Hadoop v2.7 in my mac and i am able to start the Hadoop daemons.
I would like to write the MR program using eclipse, i need some help to get the hadoop on my eclipse, i would like to know the jar files to be added and basic set up guide
The following is my Driver class code and i couldn't execute it
public class MyJobDriver extends Configured implements Tool {
#Override
public int run(String[] args) throws Exception {
Configuration conf = getConf();
JobConf job = new JobConf(conf, MyJobDriver.class);
Path in = new Path(args[0]);
Path out = new Path(args[1]);
FileInputFormat.setInputPaths(job, in);
FileOutputFormat.setOutputPath(job, out);
job.setJobName("Patent");
job.setMapperClass(InverseMapper.class);
//Input Split consist two values separated by ","
//K1 and V1 type is Text
job.setInputFormat(KeyValueTextInputFormat.class);
job.set("key.value.separator.in.input.line",",");//Everything before the separator is the key and after is the value
job.setOutputFormat(TextOutputFormat.class);//Key and value written as string and separated by tab(default)
//when k1 and k2 are od same type and V1 and V2 are of same type
//we can skip job.setMapOutputKeyClass() and job.setMapOutputValueClass()
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//jobClient communicates with the JobTrackers to start job across clusters
JobClient.runJob(job);
return 0;
}
public static void main(String[] args) throws Exception {
MyJobDriver driver = new MyJobDriver();
System.out.println("Calling the run method");
int exitCode = ToolRunner.run(driver, args);
System.exit(exitCode);
}
It is too much trouble track and retrieve the necessary jar file (there are many). Instead create a maven project in eclipse and add necessary dependencies as mentioned here https://hadoopi.wordpress.com/2013/05/25/setup-maven-project-for-hadoop-in-5mn/
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.
EntityManagerFactory can be created without a persistence unit xml using
org.eclipse.persistence.jpa.PersistenceProvider {
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
java.util.Map properties)
}
but what is the implementation class of javax.persistence.spi.PersistenceUnitInfo in eclipselink
I am struggling on this problem too. I think that a PersistenceUnitInfo must be provided by the container(i.e. Application Server). It means that Eclipselink do not create one itself. If you are using Spring ORM, it uses a DefaultPersistenceUnitManager and call its obtainPersistenceUnitInfo(String unitName) method to get a instance of PersistenceUnitInfo. The unitName must be defined in persistence.xml. This means that you still needs an xml file.
By digging into the source code of Spring ORM, I found that Spring provides several implementations of PersistenceUnitInfo. In fact they are generally a Java Bean. You may be interested in SmartPersistenceInfo, MutablePersistenceInfo and SpringPersistenceUnitInfo. View them on Github.
EDIT:
I found the implementation of Eclipselink: It's SEPersistenceUnitInfo in org.eclipse.persistence.internal.jpa.deployment. Also found the method that reads every persistence unit in the configuration xml file.
public static Set<SEPersistenceUnitInfo> getPersistenceUnits(ClassLoader loader, Map m, List<URL> jarFileUrls) {
String descriptorPath = (String) m.get(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML);
if(descriptorPath == null) {
descriptorPath = System.getProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML_DEFAULT);
}
Set<Archive> archives = findPersistenceArchives(loader, descriptorPath, jarFileUrls);
Set<SEPersistenceUnitInfo> puInfos = new HashSet();
try {
for(Archive archive : archives) {
List<SEPersistenceUnitInfo> puInfosFromArchive = getPersistenceUnits(archive, loader);
puInfos.addAll(puInfosFromArchive);
}
} finally {
for(Archive archive : archives) {
archive.close();
}
}
return puInfos;
}
Java EE platform spec 6 says : the container is responsible for finding persistence.xml condensing the information into PersistenceUnitInfo and supplying that with a call to createContainerEntityManagerFactory.
PersistenceUnitInfo is defined by the Spec, refer to the JPA spec code or JavaDoc for its implementation.
http://www.eclipse.org/eclipselink/api/2.5/javax/persistence/spi/PersistenceUnitInfo.html