From fd71d0548b5d207d8c75c93cecf81edb715d09a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wo=C5=BAniak?= Date: Tue, 16 Jul 2019 23:17:14 +0200 Subject: [PATCH] Implemented asynchronous decision engine. Resolved #210 --- .../dmn/engine/AsyncDecisionEngine.java | 38 +++++++++ .../engine/DefaultAsyncDecisionEngine.java | 54 ++++++++++++ .../AsyncDecisionEngineConfiguration.java | 36 ++++++++ .../DecisionEngineConfiguration.java | 7 +- ...faultAsyncDecisionEngineConfiguration.java | 70 ++++++++++++++++ .../DefaultDecisionEngineConfiguration.java | 2 +- ...syncDecisionEngineConfigurationSpec.groovy | 83 +++++++++++++++++++ 7 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 dmn-engine/src/main/java/org/powerflows/dmn/engine/AsyncDecisionEngine.java create mode 100644 dmn-engine/src/main/java/org/powerflows/dmn/engine/DefaultAsyncDecisionEngine.java create mode 100644 dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/AsyncDecisionEngineConfiguration.java create mode 100644 dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfiguration.java create mode 100644 dmn-engine/src/test/groovy/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfigurationSpec.groovy diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/AsyncDecisionEngine.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/AsyncDecisionEngine.java new file mode 100644 index 0000000..93a6448 --- /dev/null +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/AsyncDecisionEngine.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-present PowerFlows.org - all rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.powerflows.dmn.engine; + + +import org.powerflows.dmn.engine.model.decision.Decision; +import org.powerflows.dmn.engine.model.evaluation.result.DecisionResult; +import org.powerflows.dmn.engine.model.evaluation.variable.DecisionVariables; + +import java.util.concurrent.CompletableFuture; + +/** + * AsynchronousdDecision engine contract. + */ +public interface AsyncDecisionEngine { + + /** + * @param decision Definition of decision + * @param decisionVariables Variables used in evaluation + * @return a promise of evaluation result + */ + CompletableFuture evaluate(Decision decision, DecisionVariables decisionVariables); + +} diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/DefaultAsyncDecisionEngine.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/DefaultAsyncDecisionEngine.java new file mode 100644 index 0000000..a9d1d05 --- /dev/null +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/DefaultAsyncDecisionEngine.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-present PowerFlows.org - all rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.powerflows.dmn.engine; + +import org.powerflows.dmn.engine.model.decision.Decision; +import org.powerflows.dmn.engine.model.evaluation.result.DecisionResult; +import org.powerflows.dmn.engine.model.evaluation.variable.DecisionVariables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +/** + * Default asynchronous decision engine implementation. + */ +public class DefaultAsyncDecisionEngine implements AsyncDecisionEngine { + private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncDecisionEngine.class); + private final DecisionEngine decisionEngine; + private final ExecutorService executorService; + + public DefaultAsyncDecisionEngine(DecisionEngine decisionEngine, ExecutorService executorService) { + this.decisionEngine = decisionEngine; + this.executorService = executorService; + } + + @Override + public CompletableFuture evaluate(final Decision decision, final DecisionVariables decisionVariables) { + final CompletableFuture result = new CompletableFuture<>(); + executorService.submit(() -> { + try { + result.complete(decisionEngine.evaluate(decision, decisionVariables)); + } catch (Exception e) { + LOG.debug("Error completing decision evaluation", e); + result.completeExceptionally(e); + } + }); + + return result; + } +} diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/AsyncDecisionEngineConfiguration.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/AsyncDecisionEngineConfiguration.java new file mode 100644 index 0000000..b03da4d --- /dev/null +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/AsyncDecisionEngineConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-present PowerFlows.org - all rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.powerflows.dmn.engine.configuration; + + +import org.powerflows.dmn.engine.AsyncDecisionEngine; + +/** + * Interface for AsyncDecisionEngine configurers. + * + * @see DefaultAsyncDecisionEngineConfiguration + */ +public interface AsyncDecisionEngineConfiguration { + + /** + * Create ready to use decision engine. + * + * @return AsyncDecisionEngine instance + */ + AsyncDecisionEngine configure(); + +} diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DecisionEngineConfiguration.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DecisionEngineConfiguration.java index 1242217..00176dd 100644 --- a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DecisionEngineConfiguration.java +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DecisionEngineConfiguration.java @@ -18,12 +18,16 @@ import org.powerflows.dmn.engine.DecisionEngine; +import org.powerflows.dmn.engine.evaluator.expression.provider.binding.MethodBinding; + +import java.util.List; /** * Interface for DecisionEngine configurers. + * * @see DefaultDecisionEngineConfiguration */ -public interface DecisionEngineConfiguration { +public interface DecisionEngineConfiguration { /** * Create ready to use decision engine. @@ -32,4 +36,5 @@ public interface DecisionEngineConfiguration { */ DecisionEngine configure(); + T methodBindings(List methodBindings); } diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfiguration.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfiguration.java new file mode 100644 index 0000000..c26a49d --- /dev/null +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-present PowerFlows.org - all rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.powerflows.dmn.engine.configuration; + +import lombok.Setter; +import lombok.experimental.Accessors; +import org.powerflows.dmn.engine.AsyncDecisionEngine; +import org.powerflows.dmn.engine.DefaultAsyncDecisionEngine; +import org.powerflows.dmn.engine.evaluator.expression.provider.binding.MethodBinding; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Default DecisionEngine configurer. + * Builds and configures {@link DefaultAsyncDecisionEngine} instances. + * Allows for customisation of method binding configuration. + * Uses single thread asynchronous executor service by default {@see Executors.newSingleThreadExecutor} + * + * @see MethodBinding + */ +@Accessors(chain = true, fluent = true) +public class DefaultAsyncDecisionEngineConfiguration implements AsyncDecisionEngineConfiguration { + @Setter + private DecisionEngineConfiguration engineConfiguration; + @Setter + private ExecutorService executorService; + @Setter + private List methodBindings = Collections.emptyList(); + + @Override + public AsyncDecisionEngine configure() { + initEngineConfiguration(); + initMethodBindings(); + initExecutorService(); + + return new DefaultAsyncDecisionEngine(engineConfiguration.configure(), executorService); + } + + private void initMethodBindings() { + engineConfiguration.methodBindings(methodBindings); + } + + private void initEngineConfiguration() { + if (engineConfiguration == null) { + engineConfiguration = new DefaultDecisionEngineConfiguration(); + } + } + + private void initExecutorService() { + if (executorService == null) { + executorService = Executors.newSingleThreadExecutor(); + } + } +} diff --git a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultDecisionEngineConfiguration.java b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultDecisionEngineConfiguration.java index e8e73a5..d536ca4 100644 --- a/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultDecisionEngineConfiguration.java +++ b/dmn-engine/src/main/java/org/powerflows/dmn/engine/configuration/DefaultDecisionEngineConfiguration.java @@ -42,7 +42,7 @@ * @see MethodBinding */ @Accessors(chain = true, fluent = true) -public class DefaultDecisionEngineConfiguration implements DecisionEngineConfiguration { +public class DefaultDecisionEngineConfiguration implements DecisionEngineConfiguration { @Setter private List methodBindings = Collections.emptyList(); diff --git a/dmn-engine/src/test/groovy/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfigurationSpec.groovy b/dmn-engine/src/test/groovy/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfigurationSpec.groovy new file mode 100644 index 0000000..7d0a961 --- /dev/null +++ b/dmn-engine/src/test/groovy/org/powerflows/dmn/engine/configuration/DefaultAsyncDecisionEngineConfigurationSpec.groovy @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018-present PowerFlows.org - all rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.powerflows.dmn.engine.configuration + +import org.powerflows.dmn.engine.AsyncDecisionEngine +import org.powerflows.dmn.engine.DecisionEngine +import org.powerflows.dmn.engine.model.decision.Decision +import org.powerflows.dmn.engine.model.evaluation.result.DecisionResult +import org.powerflows.dmn.engine.model.evaluation.variable.DecisionVariables +import spock.lang.Shared +import spock.lang.Specification + +import java.util.concurrent.CompletableFuture + +class DefaultAsyncDecisionEngineConfigurationSpec extends Specification { + + @Shared + private AsyncDecisionEngineConfiguration asyncDecisionEngineConfiguration + + @Shared + private DecisionEngineConfiguration decisionEngineConfiguration + + @Shared + private DecisionEngine decisionEngine + + @Shared + private DecisionResult decisionResult + + @Shared + private Decision decision + + void setupSpec() { + decisionResult = Mock() + decisionEngine = Mock() { + evaluate(_, _) >> decisionResult + } + decisionEngineConfiguration = Mock() { + configure() >> + decisionEngine + + } + asyncDecisionEngineConfiguration = new DefaultAsyncDecisionEngineConfiguration( + engineConfiguration: decisionEngineConfiguration + ) + + decision = Mock() + } + + void 'should evaluate asynchronously'() { + given: + final DecisionVariables decisionVariables = new DecisionVariables([:]) + + when: + final AsyncDecisionEngine asyncDecisionEngine = asyncDecisionEngineConfiguration.configure() + + then: + asyncDecisionEngine != null + + when: + final CompletableFuture future = asyncDecisionEngine.evaluate(decision, decisionVariables) + + then: + future != null + DecisionResult result = future.get() + result != null + + 0 * _ + } +}