Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RHDM-2029] Executable model doesn't report an error when duplicated … #58

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ public KnowledgeBuilder buildKnowledgePackages( KieBaseModelImpl kBaseModel, Bui
}
if (compileIncludedKieBases()) {
addFiles( buildFilter, assets, getKieBaseModel( include ), includeModule, useFolders );
} else {
buildContext.addIncludeModule(include, includeModule);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

package org.drools.compiler.kie.builder.impl;

import java.util.HashMap;
import java.util.Map;

public class BuildContext {
private final ResultsImpl messages;

Expand All @@ -33,4 +36,12 @@ public ResultsImpl getMessages() {
public boolean registerResourceToBuild(String kBaseName, String resource) {
return true;
}

public void addIncludeModule(String kBaseName, InternalKieModule includeModule) {
// no op
}

public Map<String, InternalKieModule> getIncludeModules() {
return new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would better to return Collections.emptyMap() here instead of creating a new map at each invocation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed in #59

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Set;

import org.drools.compiler.kie.builder.impl.BuildContext;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.ResultsImpl;

public class CanonicalModelBuildContext extends BuildContext {
Expand All @@ -33,6 +34,8 @@ public class CanonicalModelBuildContext extends BuildContext {
private final Collection<GeneratedClassWithPackage> allGeneratedPojos = new HashSet<>();
private final Map<String, Class<?>> allCompiledClasses = new HashMap<>();

private final Map<String, InternalKieModule> includeModules = new HashMap<>();

public CanonicalModelBuildContext() { }

public CanonicalModelBuildContext(ResultsImpl messages) {
Expand Down Expand Up @@ -81,4 +84,14 @@ private String resource2Package(String resource) {
int pathEndPos = resource.lastIndexOf('/');
return pathEndPos <= 0 ? "" :resource.substring(0, pathEndPos).replace('/', '.');
}

@Override
public void addIncludeModule(String kBaseName, InternalKieModule includeModule) {
includeModules.put(kBaseName, includeModule);
}

@Override
public Map<String, InternalKieModule> getIncludeModules() {
return includeModules;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.drools.modelcompiler.builder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -29,24 +30,35 @@
import org.drools.compiler.builder.impl.KnowledgeBuilderImpl;
import org.drools.compiler.builder.impl.TypeDeclarationFactory;
import org.drools.compiler.compiler.PackageRegistry;
import org.drools.compiler.compiler.ParserError;
import org.drools.compiler.kie.builder.impl.AbstractKieModule;
import org.drools.compiler.kie.builder.impl.BuildContext;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.lang.descr.AbstractClassTypeDeclarationDescr;
import org.drools.compiler.lang.descr.CompositePackageDescr;
import org.drools.compiler.lang.descr.EnumDeclarationDescr;
import org.drools.compiler.lang.descr.GlobalDescr;
import org.drools.compiler.lang.descr.ImportDescr;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.compiler.lang.descr.RuleDescr;
import org.drools.compiler.lang.descr.TypeDeclarationDescr;
import org.drools.core.addon.TypeResolver;
import org.drools.core.definitions.InternalKnowledgePackage;
import org.drools.core.rule.ImportDeclaration;
import org.drools.core.rule.TypeDeclaration;
import org.drools.core.util.StringUtils;
import org.drools.modelcompiler.CanonicalKieModule;
import org.drools.modelcompiler.builder.errors.UnsupportedFeatureError;
import org.drools.modelcompiler.builder.generator.DRLIdGenerator;
import org.drools.modelcompiler.builder.generator.DrlxParseUtil;
import org.drools.modelcompiler.builder.generator.declaredtype.POJOGenerator;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.ResultSeverity;

import static com.github.javaparser.StaticJavaParser.parseImport;
Expand Down Expand Up @@ -303,4 +315,45 @@ public T getPackageSource(String packageName) {
protected BuildContext createBuildContext() {
return new CanonicalModelBuildContext();
}

// Overridden, because exec-model doesn't add assets of included kbase to the packageDescr
@Override
protected void validateUniqueRuleNames(final PackageDescr packageDescr) {
super.validateUniqueRuleNames(packageDescr);

// check for duplicated rule names in included kbase
Map<String, InternalKieModule> includeModules = getBuildContext().getIncludeModules();
List<String> ruleNamesInIncludeKBases = new ArrayList<>();
for (Map.Entry<String, InternalKieModule> entry : includeModules.entrySet()) {
String kBaseName = entry.getKey();
InternalKieModule includeModule = entry.getValue();
if (!(includeModule instanceof CanonicalKieModule)) {
continue;
}
CanonicalKieModule canonicalKieModule = (CanonicalKieModule) includeModule;
InternalKieModule internalKieModule = canonicalKieModule.getInternalKieModule();
if (!(internalKieModule instanceof AbstractKieModule)) {
continue;
}
AbstractKieModule abstractKieModule = (AbstractKieModule) internalKieModule;
ReleaseId internalReleaseId = abstractKieModule.getReleaseId();
KieServices ks = KieServices.Factory.get();
KieContainer kieContainer = ks.newKieContainer(internalReleaseId);
KieBase kieBase = kieContainer.getKieBase(kBaseName);
// add rule names to ruleNamesInIncludeKBases
kieBase.getKiePackages().stream()
.filter(kPkg -> kPkg.getName().equals(packageDescr.getNamespace()))
.flatMap(kPkg -> kPkg.getRules().stream())
.forEach(rule -> ruleNamesInIncludeKBases.add(rule.getName()));
Comment on lines +341 to +347
Copy link
Author

@tkobayas tkobayas Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be a naive approach to collect rule names (= build kbase and get rules), but I cannot find a way to collect them directly from the "included kbase"'s KieModule.

There may be a case that we can get cached PackageDescr/RuleDescr of the included kbase.

    KnowledgeBuilder knowledgeBuilder = includeModule.getKnowledgeBuilderForKieBase(kBaseName);
    List<PackageDescr> includePackageDescrs = ((ModelBuilderImpl) knowledgeBuilder).getPackageDescrs(packageDescr.getNamespace());

but it's not possible in case of kie-maven-plugin use case (getKnowledgeBuilderForKieBase returns InternalKnowledgeBuilder$Empty)

WDYT, @mariofusco ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but in all honesty it seems also to be a huge amount of wasteful computational resources that moreover needs to happen again and again for each and every PackageDescr check. Just to figure out if we could find a better way to do this can you please point me to what does the non-exec model version? At the moment I don't understand why the interpreted version performs the names duplication check correctly while the exec model one doesn't. Also I wonder if we can make this more efficient by delaying this check at a later stage, maybe when the KieBase is created?

Copy link
Author

@tkobayas tkobayas Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please point me to what does the non-exec model version?

https://github.com/kiegroup/drools/blob/7.x/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/AbstractKieProject.java#L237-L239

non-exec model (compileIncludedKieBases() returns true) adds files of "included kbase" module into assets, so all of them will be parsed and compiled. Thus, it can validate the rule name duplication with KnowledgeBuilderImpl.validateUniqueRuleNames.

it seems also to be a huge amount of wasteful computational resources

Yes. Now #59 creates only KiePackage, instead of KieBase, so I hope it's relatively smaller.

needs to happen again and again for each and every PackageDescr check

Thank you for pointing this out. I have moved populateIncludedRuleNameMap() out of packages loop in #59, so executed only once.

Also I wonder if we can make this more efficient by delaying this check at a later stage, maybe when the KieBase is created?

Sure, I'm going to do it as another PR. Note that it means the duplication cannot be detected at the time of kjar build by kie-maven-plugin, right?

}
for (final RuleDescr ruleDescr : packageDescr.getRules()) {
if (ruleNamesInIncludeKBases.contains(ruleDescr.getName())) {
addBuilderResult(new ParserError(ruleDescr.getResource(),
"Duplicate rule name: " + ruleDescr.getName(),
ruleDescr.getLine(),
ruleDescr.getColumn(),
packageDescr.getNamespace()));
}
}
}
}
2 changes: 1 addition & 1 deletion drools-test-coverage/test-compiler-integration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,24 @@

package org.drools.mvel.integrationtests;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

import org.drools.core.util.FileManager;
import org.drools.testcoverage.common.util.KieBaseTestConfiguration;
import org.drools.testcoverage.common.util.KieUtil;
import org.drools.testcoverage.common.util.TestParametersUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.kie.api.KieBase;
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.definition.KiePackage;
import org.kie.api.definition.rule.Rule;
Expand All @@ -40,6 +47,19 @@ public class KieBaseIncludesTest {

private final KieBaseTestConfiguration kieBaseTestConfiguration;

private FileManager fileManager;

@Before
public void setUp() throws Exception {
this.fileManager = new FileManager();
this.fileManager.setUp();
}

@After
public void tearDown() throws Exception {
this.fileManager.tearDown();
}

public KieBaseIncludesTest(final KieBaseTestConfiguration kieBaseTestConfiguration) {
this.kieBaseTestConfiguration = kieBaseTestConfiguration;
}
Expand Down Expand Up @@ -241,4 +261,80 @@ private static long getNumberOfRules(KieBase kieBase) {
}
return nrOfRules;
}

/**
* Test the inclusion of a KieBase defined in one KJAR into the KieBase of another KJAR.
* <p/>
* The 2 KieBases use the duplicate rule names, so an error should be reported
*/
@Test
public void kieBaseIncludesCrossKJarDuplicateRuleNames_shouldReportError() throws IOException {

String pomContentMain = "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
"<modelVersion>4.0.0</modelVersion>\n" +
"<groupId>org.kie</groupId>\n" +
"<artifactId>rules-main</artifactId>\n" +
"<version>1.0.0</version>\n" +
"<packaging>jar</packaging>\n" +
"<dependencies>\n" +
"<dependency>\n" +
"<groupId>org.kie</groupId>\n" +
"<artifactId>rules-sub</artifactId>\n" +
"<version>1.0.0</version>\n" +
"</dependency>\n" +
"</dependencies>\n" +
"</project>\n";

String kmoduleContentMain = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<kmodule xmlns=\"http://jboss.org/kie/6.0.0/kmodule\">\n" +
"<kbase name=\"kbaseMain\" equalsBehavior=\"equality\" default=\"true\" packages=\"rules\" includes=\"kbaseSub\">\n" +
"<ksession name=\"ksessionMain\" default=\"true\" type=\"stateful\"/>\n" +
"</kbase>\n" +
"</kmodule>";

String drlMain = "package rules\n" +
"\n" +
"rule \"RuleA\"\n" +
"when\n" +
"then\n" +
"System.out.println(\"Rule in KieBaseMain\");\n" +
"end";

String kmoduleContentSub = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<kmodule xmlns=\"http://jboss.org/kie/6.0.0/kmodule\">\n" +
"<kbase name=\"kbaseSub\" equalsBehavior=\"equality\" default=\"false\" packages=\"rules\">\n" +
"<ksession name=\"ksessionSub\" default=\"false\" type=\"stateful\"/>\n" +
"</kbase>\n" +
"</kmodule>";

String drlSub = "package rules\n" +
"\n" +
"rule \"RuleA\"\n" +
"when\n" +
"then\n" +
"System.out.println(\"Rule in KieBaseSub\");\n" +
"end";

KieServices ks = KieServices.Factory.get();
ReleaseId releaseIdSub = ks.newReleaseId("org.kie", "rules-sub", "1.0.0");

//First deploy the second KJAR on which the first one depends.
KieFileSystem kfsSub = ks.newKieFileSystem()
.generateAndWritePomXML(releaseIdSub)
.write("src/main/resources/rules/rules.drl", drlSub)
.writeKModuleXML(kmoduleContentSub);

KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsSub, true);

KieFileSystem kfsMain = ks.newKieFileSystem()
.writePomXML(pomContentMain)
.write("src/main/resources/rules/rules.drl", drlMain)
.writeKModuleXML(kmoduleContentMain);

KieBuilder kieBuilderMain = KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsMain, false);
List<Message> messages = kieBuilderMain.getResults().getMessages(Message.Level.ERROR);

assertThat(messages).as("Duplication error should be reported")
.extracting(Message::getText).anyMatch(text -> text.contains("Duplicate rule name"));
}
}
Loading