PackageBuilder.java not available in Drools 6.1.0.final - drools

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.

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

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.
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.

Guice error when trying to use Alf Xtext parser as Standalone

I am trying to write a plugin for Papyrus that converts Alf code.
I tried to use the Alf-parser that is already included in Papyrus (org.eclipse.papyrus.uml.alf.*). So I tried to instantiate the parser as written here:
public class Activator extends Plugin {
// default Activator code here ...
public String ConvertAlfToSpecSharp(String alf)
{
new org.eclipse.emf.mwe.utils.StandaloneSetup().setPlatformUri("../");
Injector injector = new AlfStandaloneSetup().createInjectorAndDoEMFRegistration();
XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class);
resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
// ...
}
}
But the first line (new org.eclipse.emf.mwe.utils.StandaloneSetup().setPlatformUri("../");) throws the following exception:
com.google.inject.CreationException: Guice creation errors:
1) Error injecting method, java.lang.IllegalStateException: No EPackages were registered for the validator org.eclipse.papyrus.uml.alf.validation.CommonJavaValidator please override and implement getEPackages().
at org.eclipse.xtext.validation.AbstractInjectableValidator.register(Unknown Source)
at org.eclipse.xtext.service.MethodBasedModule.configure(MethodBasedModule.java:55)
while locating org.eclipse.papyrus.uml.alf.validation.CommonJavaValidator
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:435)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:183)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:109)
at com.google.inject.Guice.createInjector(Guice.java:95)
at com.google.inject.Guice.createInjector(Guice.java:72)
at com.google.inject.Guice.createInjector(Guice.java:62)
at org.eclipse.papyrus.uml.alf.CommonStandaloneSetupGenerated.createInjector(CommonStandaloneSetupGenerated.java:28)
at org.eclipse.papyrus.uml.alf.CommonStandaloneSetupGenerated.createInjectorAndDoEMFRegistration(CommonStandaloneSetupGenerated.java:22)
at org.eclipse.papyrus.uml.alf.CommonStandaloneSetup.doSetup(CommonStandaloneSetup.java:23)
at org.eclipse.papyrus.uml.alf.AlfStandaloneSetupGenerated.createInjectorAndDoEMFRegistration(AlfStandaloneSetupGenerated.java:20)
at <packagenamehere>.Activator.ConvertAlfToSpecSharp(Activator.java:113)
I have no idea how to solve this, especially since I find it very hard to debug eclipse applications...
Update:
Here are links to some relevant classes (all from the org.eclipse.papyrus.uml.alf.common plugin of Papyrus plugins (link)):
org.eclipse.papyrus.uml.alf.CommonStandaloneSetup
org.eclipse.papyrus.uml.alf.CommonStandaloneSetupGenerated
org.eclipse.papyrus.uml.alf.validation.CommonJavaValidator
org.eclipse.papyrus.uml.alf.validation.AbstractCommonJavaValidator
As you can see, the method getEPackages() of AbstractCommonJavaValidator.java returns an empty list.
If you look at this AbstractAlfJavaValidator implementation, there is an EPackage which is added to the list.
As a solution, i think you should edit CommonJavaValidator.java and override getEPackages() in order to add an EPackage:
#Override
protected List<EPackage> getEPackages() {
List<EPackage> result = super.getEPackages();
// result.add(org.eclipse.papyrus.uml.alf.alf.AlfPackage.eINSTANCE);
// Edit
result.add(org.eclipse.emf.ecore.EcorePackage.eINSTANCE);
return result;
}
Edit
If you can't edit papyrus plugins, it think you should add the following before your code:
if (!EPackage.Registry.INSTANCE.containsKey("http://www.eclipse.org/papyrus/alf/Alf")) {
EPackage.Registry.INSTANCE.put("http://www.eclipse.org/papyrus/alf/Alf", org.eclipse.papyrus.uml.alf.alf.AlfPackage.eINSTANCE);
}
It will add an EPackage before the guice creation and the exception will avoided.

XText programmatically parse a DSL script into an Ecore model

I need to programmatically turn a text conform to an XText grammar into an AST conform to an Ecore meta-model generated by XText from the same grammar.
I know XText also generate the Java classes implementing such parser but I don't know either where they are and how to use it.
A complete answer to this question can be found on the Xtext page of the Eclipse wiki.
new org.eclipse.emf.mwe.utils.StandaloneSetup().setPlatformUri("../");
Injector injector = new MyDslStandaloneSetup().createInjectorAndDoEMFRegistration();
XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class);
resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
Resource resource = resourceSet.createResource(URI.createURI("dummy:/example.mydsl"));
InputStream in = new ByteArrayInputStream("type foo type bar".getBytes());
resource.load(in, resourceSet.getLoadOptions());
Model model = (Model) resource.getContents().get(0);
Change the file extension (mydsl) to your own language extension.
Here's the code:
#Inject
ParseHelper<Domainmodel> parser
def void parseDomainmodel() {
// When in a vanilla Java application (i.e. not within Eclipse),
// you need to run a global setup:
val injector = new MyDslStandaloneSetup().createInjectorAndDoEMFRegistration
injector.injectMembers(this) // sets the field 'parser'
// this is how you can use it:
val model = parser.parse(
"entity MyEntity {
parent: MyEntity
}")
val entity = model.elements.head as Entity
assertSame(entity, entity.features.head.type)
}
See also http://www.eclipse.org/Xtext/documentation.html#TutorialUnitTests.