如何使用 Java 在 KieServices 中以编程方式注册 Drools 6 自定义运算符

How to register a Drools 6 custom operator programmatically in KieServices with Java

我有一些对象链,如下所示,我想使用 Drools 6.4.0:

进行处理
@Value
public final class Node {
    private final String code;
    private final Node prev;
}

例如,要使用的示例规则如下:

rule "Initial repetition"
when
    $n1: Node(prev == null, $c: code)
    $n2: Node(prev == $n1, code == $c)
then
    System.out.println($c + ": " + $n1 + ":" + $n2);
end

Drools 已初始化,运行 使用以下代码:

private KieBase base;

public void process(List<Node> nodes) {
    initialise();
    KieSession session = base.newKieSession();
    nodes.forEach(session::insert);
    session.fireAllRules();
    session.dispose();
}

private void initialise() {
    if (base == null) {
        // Get the KIE services
        KieServices services = KieServices.Factory.get();
        // Get a virtual file system
        KieFileSystem fileSystem = services.newKieFileSystem();
        // Add a DRL file to the virtual file system
        String location = "/drools/Repetitions.drl";
        InputStream stream = getClass().getResourceAsStream(location);
        Resource resource = ResourceFactory.newInputStreamResource(stream);
        fileSystem.write("src/main/resources" + location, resource);
        // Build the virtual file system into a repository's container
        KieBuilder builder = services.newKieBuilder(fileSystem).buildAll();
        Results results = builder.getResults();
        if (results.hasMessages(ERROR)) {
            throw new RuntimeException(join("\n", results.getMessages()));
        }
        KieRepository repository = services.getRepository();
        KieContainer container = services.newKieContainer(repository.getDefaultReleaseId());
        // Get the knowledge base
        base = container.newKieBase();
    }
}

因为我必须识别每个链中第一个 Node 的任何重复,我想定义一个自定义运算符“precedes”以简化规则的起草并且能够例如写:

rule "Any repetition of first nodes"
when
    $n1: Node(prev == null, $c: code)
    $n2: Node($n1 precedes this, code == $c)
then
    System.out.println($n2);
end

我创建了 PrecedesEvaluatorPrecedesEvaluatorDefinition 如下:

public class PrecedesEvaluator extends BaseEvaluator {
    private static final long serialVersionUID = ...L;
    private final boolean isNegated; 

    public PrecedesEvaluator(ValueType type, boolean isNegated) {
        super(type, isNegated ?
                PrecedesEvaluatorDefinition.NOT_PRECEDES :
                PrecedesEvaluatorDefinition.PRECEDES);
        this.isNegated = isNegated;
    }

    @Override
    public boolean evaluate(InternalWorkingMemory workingMemory, InternalReadAccessor extractor, InternalFactHandle factHandle, FieldValue value) {
        Object nodeLeft = extractor.getValue(workingMemory, factHandle.getObject());
        return isNegated ^ evaluateUnsafe(nodeLeft, value.getValue());
    }

    @Override
    public boolean evaluate(InternalWorkingMemory workingMemory, InternalReadAccessor leftExtractor, InternalFactHandle left, InternalReadAccessor rightExtractor, InternalFactHandle right) {
        Object nodeLeft = leftExtractor.getValue(workingMemory, left.getObject());
        Object nodeRight = rightExtractor.getBigDecimalValue(workingMemory, right.getObject());
        return isNegated ^ evaluateUnsafe(nodeLeft, nodeRight);
    }

    @Override
    public boolean evaluateCachedLeft(InternalWorkingMemory workingMemory, VariableContextEntry context, InternalFactHandle right) {
        Object nodeLeft = context.getFieldExtractor().getValue(workingMemory, right.getObject());
        Object nodeRight = right.getObject();
        return isNegated ^ evaluateUnsafe(nodeLeft, nodeRight);
    }

    @Override
    public boolean evaluateCachedRight(InternalWorkingMemory workingMemory, VariableContextEntry context, InternalFactHandle left) {
        Object nodeLeft = ((ObjectVariableContextEntry) context).right;
        Object nodeRight = context.getFieldExtractor().getValue(workingMemory, left.getObject());
        return isNegated ^ evaluateUnsafe(nodeLeft, nodeRight);
    }

    private boolean evaluateUnsafe(Object nodeLeft, Object nodeRight) {
        if (!(nodeLeft instanceof Node)) {
            throw new IllegalArgumentException("'nodeLeft' can't be casted to Node: " + nodeLeft.getClass());
        }
        if (!(nodeRight instanceof Node)) {
            throw new IllegalArgumentException("'nodeRight' can't be casted to Node: " + nodeRight.getClass());
        }
        return evaluate((Node) nodeLeft, (Node) nodeRight);
    }

    private boolean evaluate(node nodeLeft, node nodeRight) {
        Node current = nodeRight;
        while (current != null) {
            if (current == null) {
                return false;
            }
            if (current == nodeLeft) {
                return true;
            }
            current = current.getPrev();
        }
        return false;
    }
}

public class PrecedesEvaluatorDefinition implements EvaluatorDefinition {
    private static final long serialVersionUID = ...L;

    protected static final String precedesOp = "precedes";

    public static Operator PRECEDES;
    public static Operator NOT_PRECEDES;
    private static String[] SUPPORTED_IDS;

    private PrecedesEvaluator evaluator;
    private PrecedesEvaluator negatedEvaluator;

    @Override
    public String[] getEvaluatorIds() {
        return new String[] {precedesOp};
    }

    @Override
    public boolean isNegatable() {
        return true;
    }

    @Override
    public Evaluator getEvaluator(ValueType type, String operatorId, boolean isNegated, String parameterText, Target leftTarget, Target rightTarget) {
        return isNegated ?
                (negatedEvaluator == null ? new PrecedesEvaluator(type, true) : negatedEvaluator) :
                (evaluator == null ? new PrecedesEvaluator(type, false) : evaluator);
    }

    @Override
    public Evaluator getEvaluator(ValueType type, String operatorId, boolean isNegated, String parameterText) {
        return getEvaluator(type, operatorId, isNegated, parameterText, Target.BOTH, Target.BOTH);
    }

    @Override
    public Evaluator getEvaluator(ValueType type, Operator operator, String parameterText) {
        return getEvaluator(type, operator.getOperatorString(), operator.isNegated(), parameterText);
    }

    @Override
    public Evaluator getEvaluator(ValueType type, Operator operator) {
        return getEvaluator(type, operator.getOperatorString(), operator.isNegated(), null);
    }

    @Override
    public boolean supportsType(ValueType type) {
        return true;
    }

    @Override
    public Target getTarget() {
        return Target.BOTH;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        throw new UnsupportedOperationException("writeExternal not usable");
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        throw new UnsupportedOperationException("readExternal not usable");
    }

    static {
        if (SUPPORTED_IDS == null) {
            PRECEDES = Operator.addOperatorToRegistry(precedesOp, false);
            NOT_PRECEDES = Operator.addOperatorToRegistry(precedesOp, true);
            SUPPORTED_IDS = new String[] {precedesOp};
        }
    }
}

我在线阅读了一些指南,并尝试以编程方式注册新的运算符,如下所示:

private void initialise() {
    if (base == null) {
        ...
        KieBaseConfiguration configuration = services.newKieBaseConfiguration();
        KieBaseOption option = EvaluatorOption.get(precedesOp, new PrecedesEvaluatorDefinition());
        configuration.setOption(option); // Wrong type
        ...
        base = container.newKieBase(configuration);
    }
}

private void initialise() {
    if (base == null) {
        KnowledgeBuilderConfiguration configuration = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
        EvaluatorOption option = EvaluatorOption.get(precedesOp, new PrecedesEvaluatorDefinition());
        configuration.setOption(option);
        ...
        base = container.newKieBase(configuration); // Wrong type!
    }
}

然而,在这两种情况下,都会发生类型不匹配并且编译失败。

所以我的问题是:我应该如何注册我的运算符以在规则中使用(请注意,如果可能,我不希望使用 XML 文件)?

以下代码有效,但使用了已弃用的 KnowledgeBase(因此这不算作答案):

private KnowledgeBase base;

public void process(List<Node> nodes) {
    initialise();
    KieSession session = base.newKieSession();
    nodes.forEach(session::insert);
    session.fireAllRules();
    session.dispose();
}

private void initialise() {
    if (base == null) {
        // Get a configuration
        KnowledgeBuilderConfiguration configuration = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
        // Instantiate the new custom operator and add it to configuration
        EvaluatorOption option = EvaluatorOption.get(precedesOp, new PrecedesEvaluatorDefinition());
        configuration.setOption(option);

        // Get a builder from the configuration
        KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder(configuration);
        // Load a DRL and add it to the builder
        String location = "/drools/Repetitions.drl";
        InputStream stream = getClass().getResourceAsStream(location);
        Resource resource = ResourceFactory.newInputStreamResource(stream);
        builder.add(resource, ResourceType.DRL);
        // Test the builder for errors
        if (builder.hasErrors()) {
            throw new RuntimeException(join("\n", builder.getErrors()));
        }

        // Get a knowledge base and fill it with the builder's content
        base = KnowledgeBaseFactory.newKnowledgeBase();
        base.addKnowledgePackages(builder.getKnowledgePackages());
    }
}

我最终使用 KieHelper 进行了以下初始化,整个初始化更加清晰。

KieServices ks = KieServices.Factory.get();
KieModuleModel kieModel = ks
    .newKieModuleModel()
    .setConfigurationProperty("drools.evaluator.precedes", PrecedesEvaluatorDefinition.class.getName());

KieBase kieBase = new KieHelper()
    .setKieModuleModel(kieModel)
    .addFromClassPath("/drools/Repetitions.drl")
    .build();

灵感来自 this test 来自 drools 本身。