Skip to content

Commit

Permalink
DROOLS-1663 Kie DMN doesn't support IMPORT decisions between DMN files (
Browse files Browse the repository at this point in the history
#1832)

* WIP

* WIP types

* Hardcoded dependency ordering but fixes compilation errors.

* With hardcoded dependency order, and hardcoded import, working execution

* .

* Dependency sorter

* import ItemDefinition and BKM from the located import model

* .

* Refactoring

* .

* .

* Guarding for DMN importType namespace only

* Externalize ResourceWithConfiguration interface

* Use wide type Collection for collection of resources

* Applying PR comments
  • Loading branch information
tarilabs authored and mariofusco committed Mar 27, 2018
1 parent 03c8c8c commit aaf8483
Show file tree
Hide file tree
Showing 23 changed files with 772 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@

package org.drools.compiler.builder.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.drools.compiler.compiler.BPMN2ProcessFactory;
import org.drools.compiler.lang.descr.CompositePackageDescr;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.core.builder.conf.impl.JaxbConfigurationImpl;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceConfiguration;
import org.kie.api.io.ResourceType;
import org.kie.api.io.ResourceWithConfiguration;
import org.kie.internal.builder.ChangeType;
import org.kie.internal.builder.CompositeKnowledgeBuilder;
import org.kie.internal.builder.ResourceChange;
import org.kie.internal.builder.ResourceChangeSet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.kie.internal.io.ResourceWithConfigurationImpl;

public class CompositeKnowledgeBuilderImpl implements CompositeKnowledgeBuilder {

Expand Down Expand Up @@ -147,11 +150,8 @@ private void buildResourceType(ResourceBuilder resourceBuilder, ResourceType res
private void buildOthers() {
try {
for (Map.Entry<ResourceType, List<ResourceDescr>> entry : resourcesByType.entrySet()) {
for (ResourceDescr resourceDescr : entry.getValue()) {
kBuilder.setAssetFilter(resourceDescr.getFilter());
kBuilder.addPackageForExternalType(resourceDescr.resource, entry.getKey(), resourceDescr.configuration);
kBuilder.setAssetFilter(null);
}
List<ResourceWithConfiguration> rds = entry.getValue().stream().map(CompositeKnowledgeBuilderImpl::descrToResourceWithConfiguration).collect(Collectors.toList());
kBuilder.addPackageForExternalType(entry.getKey(), rds);
}
} catch (RuntimeException e) {
throw e;
Expand All @@ -160,6 +160,13 @@ private void buildOthers() {
}
}

private static ResourceWithConfiguration descrToResourceWithConfiguration(ResourceDescr rd) {
return new ResourceWithConfigurationImpl(rd.resource,
rd.configuration,
kb -> ((KnowledgeBuilderImpl) kb).setAssetFilter(rd.getFilter()),
kb -> ((KnowledgeBuilderImpl) kb).setAssetFilter(null));
}

private Collection<CompositePackageDescr> buildPackageDescr() {
Map<String, CompositePackageDescr> packages = new HashMap<>();
buildResource(packages, ResourceType.DRL, ResourceToPkgDescrMapper.DRL_TO_PKG_DESCR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceConfiguration;
import org.kie.api.io.ResourceType;
import org.kie.api.io.ResourceWithConfiguration;
import org.kie.api.runtime.rule.AccumulateFunction;
import org.kie.internal.ChangeSet;
import org.kie.internal.builder.CompositeKnowledgeBuilder;
Expand Down Expand Up @@ -820,6 +821,7 @@ public void addKnowledgeResource(Resource resource,
}
}

@Deprecated
void addPackageForExternalType(Resource resource,
ResourceType type,
ResourceConfiguration configuration) throws Exception {
Expand All @@ -836,6 +838,19 @@ void addPackageForExternalType(Resource resource,
throw new RuntimeException("Unknown resource type: " + type);
}
}

void addPackageForExternalType(ResourceType type, List<ResourceWithConfiguration> resources) throws Exception {
KieAssemblers assemblers = ServiceRegistry.getInstance().get(KieAssemblers.class);

KieAssemblerService assembler = assemblers.getAssemblers().get(type);

if (assembler != null) {
assembler.addResources(this, resources, type);
} else {
throw new RuntimeException("Unknown resource type: " + type);
}
}

public void addPackageFromPMML(Resource resource,
ResourceType type,
ResourceConfiguration configuration) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,31 @@

package org.kie.dmn.api.core;

import java.io.Reader;
import java.util.Collection;
import java.util.Collections;

import org.kie.api.io.Resource;
import org.kie.dmn.api.marshalling.v1_1.DMNExtensionRegister;
import org.kie.dmn.model.v1_1.Definitions;

import java.io.Reader;

public interface DMNCompiler {

DMNModel compile( Resource resource );
default DMNModel compile(Resource resource) {
return compile(resource, Collections.emptyList());
}

DMNModel compile(Resource resource, Collection<DMNModel> dmnModels);

default DMNModel compile(Reader source) {
return compile(source, Collections.emptyList());
}

DMNModel compile(Reader source, Collection<DMNModel> dmnModels);

default DMNModel compile(Definitions dmndefs) {
return compile(dmndefs, Collections.emptyList());
}

DMNModel compile( Reader source );
DMNModel compile(Definitions dmndefs, Collection<DMNModel> dmnModels);

DMNModel compile(Definitions dmndefs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public enum DMNMessageType {
UNSUPPORTED_ELEMENT( "The referenced element is not supported by the implementation", Tag.COMPILATION, Tag.DMN_CORE ),
REQ_NOT_FOUND( "The referenced node was not found", Tag.COMPILATION, Tag.DMN_CORE ),
IMPORT_NOT_FOUND("The referenced import was not found", Tag.COMPILATION, Tag.DMN_CORE),
TYPE_REF_NOT_FOUND( "The listed type reference could not be resolved", Tag.COMPILATION, Tag.DMN_CORE ),
TYPE_DEF_NOT_FOUND( "The listed type definition was not found", Tag.COMPILATION, Tag.DMN_CORE ),
INVALID_NAME( "The listed name is not a valid FEEL identifier", Tag.VALIDATION, Tag.DMN_VALIDATOR, Tag.COMPILATION, Tag.DMN_CORE ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ public interface DMNNode {

String getName();

String getModelNamespace();

String getModelName();

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.drools.compiler.builder.impl.KnowledgeBuilderImpl;
import org.drools.compiler.compiler.PackageRegistry;
import org.drools.compiler.lang.descr.PackageDescr;
Expand All @@ -32,15 +35,23 @@
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceConfiguration;
import org.kie.api.io.ResourceType;
import org.kie.api.io.ResourceWithConfiguration;
import org.kie.dmn.api.core.DMNCompiler;
import org.kie.dmn.api.core.DMNCompilerConfiguration;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.api.marshalling.v1_1.DMNMarshaller;
import org.kie.dmn.core.api.DMNFactory;
import org.kie.dmn.core.compiler.DMNCompilerConfigurationImpl;
import org.kie.dmn.core.compiler.DMNCompilerImpl;
import org.kie.dmn.core.compiler.DMNProfile;
import org.kie.dmn.core.compiler.ImportDMNResolverUtil;
import org.kie.dmn.core.compiler.ImportDMNResolverUtil.ImportType;
import org.kie.dmn.core.compiler.profiles.ExtendedDMNProfile;
import org.kie.dmn.core.impl.DMNKnowledgeBuilderError;
import org.kie.dmn.core.impl.DMNPackageImpl;
import org.kie.dmn.feel.util.Either;
import org.kie.dmn.model.v1_1.Definitions;
import org.kie.dmn.model.v1_1.Import;
import org.kie.internal.builder.ResultSeverity;
import org.kie.internal.utils.ChainedProperties;
import org.slf4j.Logger;
Expand All @@ -61,12 +72,68 @@ public ResourceType getResourceType() {
}

@Override
public void addResource(Object kbuilder, Resource resource, ResourceType type, ResourceConfiguration configuration)
throws Exception {
public void addResources(Object kbuilder, Collection<ResourceWithConfiguration> resources, ResourceType type) throws Exception {
KnowledgeBuilderImpl kbuilderImpl = (KnowledgeBuilderImpl) kbuilder;
DMNCompilerImpl dmnCompiler = (DMNCompilerImpl) kbuilderImpl.getCachedOrCreate(DMN_COMPILER_CACHE_KEY, () -> getCompiler(kbuilderImpl));
DMNMarshaller dmnMarshaller = dmnCompiler.getMarshaller();
if (resources.size() == 1) {
// quick path:
internalAddResource(kbuilderImpl, dmnCompiler, resources.iterator().next(), Collections.emptyList());
return;
}
List<DMNResource> dmnResources = new ArrayList<>();
for (ResourceWithConfiguration r : resources) {
Definitions definitions = dmnMarshaller.unmarshal(r.getResource().getReader());
QName modelID = new QName(definitions.getNamespace(), definitions.getName());
DMNResource dmnResource = new DMNResource(modelID, r, definitions);
dmnResources.add(dmnResource);
}
// enrich with imports
for (DMNResource r : dmnResources) {
for (Import i : r.getDefinitions().getImport()) {
if (ImportDMNResolverUtil.whichImportType(i) == ImportType.DMN) {
Either<String, DMNResource> resolvedResult = ImportDMNResolverUtil.resolveImportDMN(i, dmnResources, DMNResource::getModelID);
DMNResource located = resolvedResult.getOrElseThrow(RuntimeException::new);
r.addDependency(located.getModelID());
}
}
}
List<DMNResource> sortedDmnResources = DMNResourceDependenciesSorter.sort(dmnResources);
Collection<DMNModel> dmnModels = new ArrayList<>();

for (DMNResource dmnRes : sortedDmnResources) {
DMNModel dmnModel = internalAddResource(kbuilderImpl, dmnCompiler, dmnRes.getResAndConfig(), dmnModels);
dmnModels.add(dmnModel);
}
}

private DMNModel internalAddResource(KnowledgeBuilderImpl kbuilder, DMNCompiler dmnCompiler, ResourceWithConfiguration r, Collection<DMNModel> dmnModels) throws Exception {
r.getBeforeAdd().accept(kbuilder);
DMNModel dmnModel = compileResourceToModel(kbuilder, dmnCompiler, r.getResource(), dmnModels);
r.getAfterAdd().accept(kbuilder);
return dmnModel;
}

@Override
public void addResource(Object kbuilder, Resource resource, ResourceType type, ResourceConfiguration configuration) throws Exception {
logger.warn("invoked legacy addResource (no control on the order of the assembler compilation): " + resource.getSourcePath());
KnowledgeBuilderImpl kbuilderImpl = (KnowledgeBuilderImpl) kbuilder;
DMNCompiler dmnCompiler = kbuilderImpl.getCachedOrCreate( DMN_COMPILER_CACHE_KEY, () -> getCompiler( kbuilderImpl ) );

DMNModel model = dmnCompiler.compile(resource);
Collection<DMNModel> dmnModels = new ArrayList<>();
for (PackageRegistry pr : kbuilderImpl.getPackageRegistry().values()) {
ResourceTypePackage resourceTypePackage = pr.getPackage().getResourceTypePackages().get(ResourceType.DMN);
if (resourceTypePackage != null) {
DMNPackageImpl dmnpkg = (DMNPackageImpl) resourceTypePackage;
dmnModels.addAll(dmnpkg.getAllModels().values());
}
}

compileResourceToModel(kbuilderImpl, dmnCompiler, resource, dmnModels);
}

private DMNModel compileResourceToModel(KnowledgeBuilderImpl kbuilderImpl, DMNCompiler dmnCompiler, Resource resource, Collection<DMNModel> dmnModels) {
DMNModel model = dmnCompiler.compile(resource, dmnModels);
if( model != null ) {
String namespace = model.getNamespace();

Expand All @@ -92,6 +159,7 @@ public void addResource(Object kbuilder, Resource resource, ResourceType type, R
kbuilderImpl.addBuilderResult(new DMNKnowledgeBuilderError(ResultSeverity.ERROR, resource, "Unable to compile DMN model for the resource"));
logger.error( "Unable to compile DMN model for resource {}", resource.getSourcePath() );
}
return model;
}

private List<DMNProfile> getDMNProfiles(KnowledgeBuilderImpl kbuilderImpl) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.kie.dmn.core.assembler;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;

import org.kie.api.io.ResourceWithConfiguration;
import org.kie.dmn.model.v1_1.Definitions;

public class DMNResource {

private final QName modelID;
private final ResourceWithConfiguration resAndConfig;
private final Definitions definitions;
private final List<QName> dependencies = new ArrayList<>();

public DMNResource(QName modelID, ResourceWithConfiguration resAndConfig, Definitions definitions) {
this.modelID = modelID;
this.resAndConfig = resAndConfig;
this.definitions = definitions;
}

public QName getModelID() {
return modelID;
}

public ResourceWithConfiguration getResAndConfig() {
return resAndConfig;
}

public Definitions getDefinitions() {
return definitions;
}

public void addDependency(QName dep) {
this.dependencies.add(dep);
}

public void addDependencies(List<QName> deps) {
this.dependencies.addAll(deps);
}

public List<QName> getDependencies() {
return dependencies;
}

@Override
public String toString() {
return "DMNResource [modelID=" + modelID + ", resource=" + resAndConfig.getResource().getSourcePath() + "]";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.kie.dmn.core.assembler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class DMNResourceDependenciesSorter {

/**
* Return a new list of DMNResource sorted by dependencies (required dependencies comes first)
*/
public static List<DMNResource> sort(List<DMNResource> ins) {
// In a graph A -> B -> {C, D}
// showing that A requires B, and B requires C,D
// then a depth-first visit would satisfy required ordering, for example a valid depth first visit is also a valid sort here: C, D, B, A.
Collection<DMNResource> visited = new ArrayList<>(ins.size());
List<DMNResource> dfv = new ArrayList<>(ins.size());

for (DMNResource node : ins) {
if (!visited.contains(node)) {
dfVisit(node, ins, visited, dfv);
}
}

return dfv;
}

/**
* Performs a depth first visit, but keeping a separate reference of visited/visiting nodes, _also_ to avoid potential issues of circularities.
*/
private static void dfVisit(DMNResource node, List<DMNResource> allNodes, Collection<DMNResource> visited, List<DMNResource> dfv) {
if (visited.contains(node)) {
throw new RuntimeException("Circular dependency detected: " + visited + " , and again to: " + node);
}
visited.add(node);

List<DMNResource> neighbours = node.getDependencies().stream()
.flatMap(dep -> allNodes.stream().filter(r -> r.getModelID().equals(dep)))
.collect(Collectors.toList());
for (DMNResource n : neighbours) {
if (!visited.contains(n)) {
dfVisit(n, allNodes, visited, dfv);
}
}

dfv.add(node);
}
}
Loading

0 comments on commit aaf8483

Please sign in to comment.