aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main')
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/EngineParameterConstants.java34
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/EngineParameters.java71
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/ExecutorParameters.java59
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/TaskParameters.java77
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/context/ApexInternalContext.java233
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/context/package-info.java28
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/ApexEngine.java138
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/EnEventListener.java42
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineConstants.java41
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineFactory.java43
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImpl.java532
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/StateMachineHandler.java188
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/package-info.java28
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/engine/package-info.java30
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/event/EnEvent.java291
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/event/EnException.java51
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/event/EnField.java129
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/event/package-info.java28
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/Executor.java167
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/ExecutorFactory.java68
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/StateExecutor.java357
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutor.java236
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutor.java260
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/StateOutput.java217
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskExecutor.java334
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutor.java236
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AbstractExecutionContext.java87
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacade.java103
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacade.java141
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContext.java164
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContext.java195
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContext.java152
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/context/package-info.java35
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineException.java51
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeException.java51
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/package-info.java27
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/ExecutorFactoryImpl.java223
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/package-info.java27
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/executor/package-info.java27
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/monitoring/EventMonitor.java118
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/monitoring/package-info.java28
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/engine/package-info.java30
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/infrastructure/messaging/MessagingException.java49
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ApplicationThreadFactory.java118
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ThreadUtilities.java52
-rw-r--r--core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/package-info.java27
46 files changed, 5623 insertions, 0 deletions
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameterConstants.java b/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameterConstants.java
new file mode 100644
index 000000000..3cd283b74
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameterConstants.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * This class holds constants used when managing engine parameter groups in apex.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class EngineParameterConstants {
+ public static final String MAIN_GROUP_NAME = "ENGINE_PARAMETERS";
+ public static final String EXECUTOR_GROUP_NAME = "EXECUTOR_PARAMETERS";
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameters.java b/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameters.java
new file mode 100644
index 000000000..3f8243828
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/EngineParameters.java
@@ -0,0 +1,71 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2020,2022 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.apex.context.parameters.ContextParameters;
+import org.onap.policy.common.parameters.ParameterGroupImpl;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.common.parameters.annotations.Valid;
+
+/**
+ * This class holds the parameters for a single Apex engine. This parameter class holds parameters for context schemas
+ * and context albums for the engine and a map of the logic flavour executors defined for the engine and the parameters
+ * for each of those executors.
+ *
+ * <p>The context parameters for the engine are held in a {@link ContextParameters} instance. This instance holds the
+ * parameters for context schema handling that will be used by the engine as well as the context album distribution and
+ * locking parameters.
+ *
+ * <p>In Apex, an engine can be configured to use many logic flavours. The executors for each logic flavour are
+ * identified by their name. Each logic flavour executor must have an instance of {@link ExecutorParameters} defined for
+ * it, which specifies the executor plugins to use for that logic flavour executor and specific parameters for those
+ * executor plugins.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@Getter
+@Setter
+@NotNull
+public class EngineParameters extends ParameterGroupImpl {
+ private @Valid ContextParameters contextParameters = new ContextParameters();
+
+
+ // A map of parameters for executors of various logic types
+ private Map<String, @NotNull @Valid ExecutorParameters> executorParameterMap = new TreeMap<>();
+
+ // A list of parameters to be passed to the task, so that they can be used in the logic
+ private List<@NotNull @Valid TaskParameters> taskParameters = new ArrayList<>();
+
+ /**
+ * Constructor to create an engine parameters instance and register the instance with the parameter service.
+ */
+ public EngineParameters() {
+ super(EngineParameterConstants.MAIN_GROUP_NAME);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/ExecutorParameters.java b/core/src/main/java/org/onap/policy/apex/core/engine/ExecutorParameters.java
new file mode 100644
index 000000000..f7dc5dd0b
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/ExecutorParameters.java
@@ -0,0 +1,59 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.ParameterGroupImpl;
+
+/**
+ * This class provides the executors for a logic flavour. Plugin classes for execution of task
+ * logic, task selection logic, and state finalizer logic for the logic flavour must be specified.
+ *
+ * <p>Specializations of this class may provide extra parameters for their specific logic flavour
+ * executors.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@Getter
+@Setter
+public class ExecutorParameters extends ParameterGroupImpl {
+ // Executor Plugin classes for executors
+ private String taskExecutorPluginClass;
+ private String taskSelectionExecutorPluginClass;
+ private String stateFinalizerExecutorPluginClass;
+
+ /**
+ * Constructor to create an executor parameters instance and register the instance with the
+ * parameter service.
+ */
+ public ExecutorParameters() {
+ super(EngineParameterConstants.EXECUTOR_GROUP_NAME);
+ }
+
+ @Override
+ public String toString() {
+ return "ExecutorParameters [name=" + getName() + ", taskExecutorPluginClass=" + taskExecutorPluginClass
+ + ", taskSelectionExecutorPluginClass=" + taskSelectionExecutorPluginClass
+ + ", stateFinalizerExecutorPluginClass=" + stateFinalizerExecutorPluginClass + "]";
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/TaskParameters.java b/core/src/main/java/org/onap/policy/apex/core/engine/TaskParameters.java
new file mode 100644
index 000000000..248110419
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/TaskParameters.java
@@ -0,0 +1,77 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.onap.policy.common.parameters.BeanValidator;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+/**
+ * This class provides the configurable parameters for Apex Tasks.
+ *
+ * @author Ajith Sreekumar (ajith.sreekumar@est.tech)
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+public class TaskParameters {
+ private String name = "taskParameters";
+
+ // If taskId is not specified, then the taskParameter is added to all tasks in the engine.
+ private String taskId;
+
+ @NotNull
+ @NotBlank
+ private String key;
+ @NotNull
+ @NotBlank
+ private String value;
+
+ /**
+ * Full constructor.
+ *
+ * @param key the task parameter key
+ * @param value the task parameter value
+ * @param taskId the task ID of this task parameter
+ */
+ public TaskParameters(String key, String value, String taskId) {
+ this();
+ this.key = key;
+ this.value = value;
+ this.taskId = taskId;
+ }
+
+ /**
+ * Validates the parameters.
+ *
+ * @param resultName name of the result
+ *
+ * @return the validation result
+ */
+ public ValidationResult validate(String resultName) {
+ return new BeanValidator().validateTop(resultName, this);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/context/ApexInternalContext.java b/core/src/main/java/org/onap/policy/apex/core/engine/context/ApexInternalContext.java
new file mode 100644
index 000000000..1fee1971e
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/context/ApexInternalContext.java
@@ -0,0 +1,233 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.context;
+
+import com.google.common.collect.Maps;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextAlbum;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.context.Distributor;
+import org.onap.policy.apex.context.impl.distribution.DistributorFactory;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConceptGetter;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConceptGetterImpl;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.contextmodel.concepts.AxContextAlbum;
+import org.onap.policy.apex.model.contextmodel.concepts.AxContextAlbums;
+import org.onap.policy.apex.model.contextmodel.handling.ContextComparer;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel;
+import org.onap.policy.apex.model.utilities.comparison.KeyedMapDifference;
+
+/**
+ * This class manages the internal context for an Apex engine. This class is not thread safe and need not be because
+ * each Context object is owned by one and only one ApexEngine, which runs in a single thread and only runs one policy
+ * at a time. Therefore there is only ever one policy using a Context object at a time. The currentPolicyContextAlbum is
+ * set on the Context object by the StateMachineExecutor each time a policy is triggered.
+ *
+ * @author Liam Fallon
+ */
+public class ApexInternalContext implements AxConceptGetter<ContextAlbum> {
+ // The key of the currently running Apex model
+ @Getter
+ private AxArtifactKey key;
+
+ // The context albums being used in this engine
+ private final NavigableMap<AxArtifactKey, ContextAlbum> contextAlbums =
+ Maps.synchronizedNavigableMap(new TreeMap<AxArtifactKey, ContextAlbum>());
+
+ // The internal context uses a context distributor to handle distribution of context across multiple instances
+ private Distributor contextDistributor = null;
+
+ // The key of the current policy, used to return the correct policy context album to the user
+ private AxArtifactKey currentPolicyKey = null;
+
+ /**
+ * Constructor, instantiate the context object from the Apex model.
+ *
+ * @param apexPolicyModel the apex model
+ * @throws ContextException On errors on context setting
+ */
+ public ApexInternalContext(final AxPolicyModel apexPolicyModel) throws ContextException {
+ if (apexPolicyModel == null) {
+ throw new ContextException("internal context update failed, supplied model is null");
+ }
+ apexPolicyModel.register();
+
+ // The context distributor used to distribute context across policy engine instances
+ contextDistributor = new DistributorFactory().getDistributor(apexPolicyModel.getKey());
+
+ // Set up the context albums for this engine
+ for (final AxArtifactKey contextAlbumKey : ModelService.getModel(AxContextAlbums.class).getAlbumsMap()
+ .keySet()) {
+ contextAlbums.put(contextAlbumKey, contextDistributor.createContextAlbum(contextAlbumKey));
+ }
+
+ // Record the key of the current model
+ key = apexPolicyModel.getKey();
+ }
+
+ /**
+ * Get the context albums of the engine.
+ *
+ * @return the context albums
+ */
+ public Map<AxArtifactKey, ContextAlbum> getContextAlbums() {
+ return contextAlbums;
+ }
+
+ /**
+ * Update the current context so that it aligns with this incoming model, transferring context values if they exist
+ * in the new model.
+ *
+ * @param newPolicyModel The new incoming Apex model to use for context
+ * @param isSubsequentInstance if the current worker instance being updated is not the first one
+ * @throws ContextException On errors on context setting
+ */
+ public void update(final AxPolicyModel newPolicyModel, boolean isSubsequentInstance) throws ContextException {
+ if (newPolicyModel == null) {
+ throw new ContextException("internal context update failed, supplied model is null");
+ }
+ // context is shared between all the engine instances
+ // during model update context album only needs to be updated for the first instance.
+ // remaining engine instances can just copy the context
+ if (isSubsequentInstance) {
+ contextAlbums.clear();
+ for (AxArtifactKey contextAlbumKey : ModelService.getModel(AxContextAlbums.class).getAlbumsMap().keySet()) {
+ contextAlbums.put(contextAlbumKey, contextDistributor.createContextAlbum(contextAlbumKey));
+ }
+ key = newPolicyModel.getKey();
+ return;
+ }
+ // Get the differences between the existing context and the new context
+ final KeyedMapDifference<AxArtifactKey, AxContextAlbum> contextDifference =
+ new ContextComparer().compare(ModelService.getModel(AxContextAlbums.class), newPolicyModel.getAlbums());
+
+
+ // Handle the updated maps
+ for (final Entry<AxArtifactKey, List<AxContextAlbum>> contextAlbumEntry : contextDifference.getDifferentValues()
+ .entrySet()) {
+ // Compare the updated maps
+ final AxContextAlbum currentContextAlbum = contextAlbumEntry.getValue().get(0);
+ final AxContextAlbum newContextAlbum = contextAlbumEntry.getValue().get(1);
+
+ // Check that the schemas are the same on the old and new context albums
+ if (!currentContextAlbum.getItemSchema().equals(newContextAlbum.getItemSchema())) {
+ // The schema is different, throw an exception because the schema should not change if the key of the
+ // album has not changed
+ throw new ContextException("internal context update failed on context album \""
+ + contextAlbumEntry.getKey().getId() + "\" in model \"" + key.getId() + "\", schema \""
+ + currentContextAlbum.getItemSchema().getId()
+ + "\" on existing context model does not equal schema \""
+ + newContextAlbum.getItemSchema().getId() + "\" on incoming model");
+ }
+ }
+
+ // Remove maps that are no longer used
+ for (final Entry<AxArtifactKey, AxContextAlbum> removedContextAlbumEntry : contextDifference.getLeftOnly()
+ .entrySet()) {
+ contextDistributor.removeContextAlbum(removedContextAlbumEntry.getKey());
+ contextAlbums.remove(removedContextAlbumEntry.getKey());
+ }
+
+ // We switch over to the new Apex model
+ newPolicyModel.register();
+
+ // Set up the new context albums
+ for (final AxArtifactKey contextAlbumKey : contextDifference.getRightOnly().keySet()) {
+ // In case if a context album is part of previous and current model, but needs to be cleared
+ // for example, due to a major version change
+ if (contextAlbums.containsKey(contextAlbumKey)) {
+ contextDistributor.removeContextAlbum(contextAlbumKey);
+ }
+ contextAlbums.put(contextAlbumKey, contextDistributor.createContextAlbum(contextAlbumKey));
+ }
+
+ // Record the key of the current model
+ key = newPolicyModel.getKey();
+ }
+
+ /**
+ * Clear the internal context.
+ *
+ * @throws ContextException on clearing errors
+ */
+ public void clear() throws ContextException {
+ // Clear all context in the distributor
+ contextDistributor.clear();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "ApexInternalContext [contextAlbums=" + contextAlbums + ", contextDistributor=" + contextDistributor
+ + ", currentPolicyKey=" + currentPolicyKey + "]";
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ContextAlbum get(final AxArtifactKey conceptKey) {
+ return new AxConceptGetterImpl<>(contextAlbums).get(conceptKey);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ContextAlbum get(final String conceptKeyName) {
+ return new AxConceptGetterImpl<>(contextAlbums).get(conceptKeyName);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ContextAlbum get(final String conceptKeyName, final String conceptKeyVersion) {
+ return new AxConceptGetterImpl<>(contextAlbums).get(conceptKeyName, conceptKeyVersion);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Set<ContextAlbum> getAll(final String conceptKeyName) {
+ return new AxConceptGetterImpl<>(contextAlbums).getAll(conceptKeyName);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Set<ContextAlbum> getAll(final String conceptKeyName, final String conceptKeyVersion) {
+ return new AxConceptGetterImpl<>(contextAlbums).getAll(conceptKeyName, conceptKeyVersion);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/context/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/context/package-info.java
new file mode 100644
index 000000000..887914ee0
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/context/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Manages the context albums that an APEX engine requires during execution. It uses the policy model of the engine to
+ * determine what context albums the engine requires.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.context;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/ApexEngine.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/ApexEngine.java
new file mode 100644
index 000000000..4c4166380
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/ApexEngine.java
@@ -0,0 +1,138 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine;
+
+import java.util.Map;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.enginemodel.concepts.AxEngineModel;
+import org.onap.policy.apex.model.enginemodel.concepts.AxEngineState;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel;
+
+/**
+ * The Interface ApexEngine is used to control the execution of a single Apex engine thread. This
+ * engine instance executes the policies in an {@link AxPolicyModel}, which defines the policies
+ * that are executed by the engine and the context in which they execute. Many instances of an Apex
+ * engine may run on the same Apex model, in which case they operate the same policy set in parallel
+ * over the same context. When the {@code handleEvent} method is passed to the Apex engine, the
+ * engine executes the policy triggered by that event. A single Apex engine instance does not
+ * executed multiple policies in parallel, it receives a trigger event and executes the policy for
+ * that event to completion before it is available to execute another policy.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public interface ApexEngine {
+ /**
+ * Update the Apex model to be used by the Apex engine. The engine must be in state "STOPPED"
+ * when the model is updated. The engine will replace the current model with the incoming model
+ * if the model of the engine was previously updated and the value of common context is
+ * transferred if there is common context in the old and new models.
+ *
+ * @param apexModel the apex model
+ * @param isSubsequentInstance if the current worker instance being updated is not the first one
+ * @throws ApexException on model update errors
+ */
+ void updateModel(AxPolicyModel apexModel, boolean isSubsequentInstance) throws ApexException;
+
+ /**
+ * Starts an Apex engine so that it can receive events.
+ *
+ * @throws ApexException on start errors
+ */
+ void start() throws ApexException;
+
+ /**
+ * Stops an Apex engine in an orderly way. This method must be called prior to model updates.
+ *
+ * @throws ApexException on stop errors
+ */
+ void stop() throws ApexException;
+
+ /**
+ * Clears all models and data from an Apex engine. The engine must be stopped.
+ *
+ * @throws ApexException on clear errors
+ */
+ void clear() throws ApexException;
+
+ /**
+ * This method constructs an event with the correct event context so that it can later be sent
+ * to the Apex engine.
+ *
+ * @param eventKey The key of the event in the Apex model
+ * @return the created event
+ */
+ EnEvent createEvent(AxArtifactKey eventKey);
+
+ /**
+ * This method passes an event to the Apex model to invoke a policy. If the event matches a
+ * policy, then that policy is executed.
+ *
+ * @param incomingEvent the incoming event
+ * @return return true if a policy was invoked without error, otherwise false.
+ */
+ boolean handleEvent(EnEvent incomingEvent);
+
+ /**
+ * A method to add a call back listener class that listens for action events from the engine.
+ *
+ * @param listenerName the unique name of the listener
+ * @param listener is an instance of type {@link EnEventListener}
+ */
+ void addEventListener(String listenerName, EnEventListener listener);
+
+ /**
+ * A method to remove a call back listener class.
+ *
+ * @param listenerName the name of the listener to remove
+ */
+ void removeEventListener(String listenerName);
+
+ /**
+ * Get the artifact key of the engine.
+ *
+ * @return the artifact key
+ */
+ AxArtifactKey getKey();
+
+ /**
+ * Get the state of the engine.
+ *
+ * @return the engine state
+ */
+ AxEngineState getState();
+
+ /**
+ * Get the engine status information, this is just the engine state.
+ *
+ * @return the Apex status information
+ */
+ AxEngineModel getEngineStatus();
+
+ /**
+ * Get the engine run time information, the status and context.
+ *
+ * @return the Apex runtime information
+ */
+ Map<AxArtifactKey, Map<String, Object>> getEngineContext();
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/EnEventListener.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/EnEventListener.java
new file mode 100644
index 000000000..12ba12665
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/EnEventListener.java
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine;
+
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
+
+/**
+ * This interface is used by users of an Apex engine to receive action events being emitted by the engine.
+ *
+ * @author Liam Fallon
+ *
+ */
+@FunctionalInterface
+public interface EnEventListener {
+
+ /**
+ * This method is called when an Apex engine emits an event.
+ *
+ * @param enEvent the engine event
+ * @throws ApexException the apex exception
+ */
+ void onEnEvent(EnEvent enEvent) throws ApexException;
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineConstants.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineConstants.java
new file mode 100644
index 000000000..cba2dc9f6
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineConstants.java
@@ -0,0 +1,41 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine.impl;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * Constants for the Apex engine.
+ *
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ApexEngineConstants {
+ /**
+ * The amount of milliseconds to wait for the current Apex engine to timeout on engine stop
+ * requests. If the timeout is exceeded, the stop aborts.
+ */
+ public static final int STOP_EXECUTION_WAIT_TIMEOUT = 3000;
+
+ /** The wait increment (or pause time) when waiting for the Apex engine to stop. */
+ public static final int APEX_ENGINE_STOP_EXECUTION_WAIT_INCREMENT = 100;
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineFactory.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineFactory.java
new file mode 100644
index 000000000..754181485
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineFactory.java
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine.impl;
+
+import org.onap.policy.apex.core.engine.engine.ApexEngine;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+
+/**
+ * A factory class to create APEX engines of a given type. As there is only a single type of Apex
+ * engine in existence, this class is trivial.
+ *
+ * @author Liam Fallon
+ */
+public class ApexEngineFactory {
+
+ /**
+ * Create an Apex engine implementation.
+ *
+ * @param key the key
+ * @return the apex engine
+ */
+ public ApexEngine createApexEngine(final AxArtifactKey key) {
+ return new ApexEngineImpl(key);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImpl.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImpl.java
new file mode 100644
index 000000000..35139bfe4
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImpl.java
@@ -0,0 +1,532 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021-2022 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine.impl;
+
+import static org.onap.policy.common.utils.validation.Assertions.argumentNotNull;
+
+import io.prometheus.client.Gauge;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextAlbum;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.engine.ApexEngine;
+import org.onap.policy.apex.core.engine.engine.EnEventListener;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.core.infrastructure.threading.ThreadUtilities;
+import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
+import org.onap.policy.apex.model.basicmodel.concepts.ApexRuntimeException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.enginemodel.concepts.AxEngineModel;
+import org.onap.policy.apex.model.enginemodel.concepts.AxEngineState;
+import org.onap.policy.apex.model.enginemodel.concepts.AxEngineStats;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateOutput;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateTaskOutputType;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateTaskReference;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.onap.policy.common.utils.resources.PrometheusUtils;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This class controls the thread of execution of a single engine in an Apex system. An engine is a single thread in a
+ * pool of engines that are running a set of policies. An engine is either inactive, waiting for a policy to be
+ * triggered or executing a policy. The engine runs off a queue of triggers that trigger its state machine. If the queue
+ * is empty, it waits for the next trigger. The Apex engine holds its state machine in a {@link StateMachineHandler}
+ * instance and uses its state machine handler to execute events.
+ *
+ * @author Liam Fallon
+ */
+public class ApexEngineImpl implements ApexEngine {
+
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexEngineImpl.class);
+
+ // Register state changes with prometheus
+ static final Gauge ENGINE_STATE = Gauge.build().namespace(PrometheusUtils.PdpType.PDPA.getNamespace())
+ .name("engine_state").labelNames("engine_instance_id")
+ .help("State of the APEX engine as integers mapped as - 0:UNDEFINED, 1:STOPPED, 2:READY,"
+ + " 3:EXECUTING, 4:STOPPING").register();
+
+ // Recurring string constants
+ private static final String UPDATE_MODEL = "updateModel()<-";
+ private static final String START = "start()<-";
+ private static final String STOP = "stop()<-";
+
+ // The artifact key of this engine
+ @Getter
+ private final AxArtifactKey key;
+
+ // The state of this engine
+ @Getter
+ private AxEngineState state = AxEngineState.STOPPED;
+ private final Object stateLockObj = new Object();
+
+ // call back listeners
+ private final Map<String, EnEventListener> eventListeners = new LinkedHashMap<>();
+
+ // The context of this engine
+ @Getter
+ private ApexInternalContext internalContext = null;
+
+ // The state machines
+ private StateMachineHandler stateMachineHandler = null;
+
+ // Statistics on engine execution
+ private final AxEngineStats engineStats;
+
+ /**
+ * Constructor, instantiate the engine with its state machine table.
+ *
+ * @param key the key of the engine
+ */
+ protected ApexEngineImpl(final AxArtifactKey key) {
+ argumentNotNull(key, "AxArtifactKey may not be null");
+
+ LOGGER.entry("ApexEngine()->{}, {}", key.getId(), state);
+
+ this.key = key;
+
+ // Set up statistics collection
+ engineStats = new AxEngineStats();
+ engineStats.setKey(new AxReferenceKey(key, "_EngineStats"));
+
+ LOGGER.exit("ApexEngine()<-" + key.getId() + "," + state);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void updateModel(final AxPolicyModel apexModel, final boolean isSubsequentInstance) throws ApexException {
+ updateStatePrometheusMetric();
+ if (apexModel != null) {
+ LOGGER.entry("updateModel()->{}, apexPolicyModel {}", key.getId(), apexModel.getKey().getId());
+ } else {
+ throw new ApexException(UPDATE_MODEL + key.getId() + ", Apex model is not defined, it has a null value");
+ }
+
+ // The engine must be stopped in order to do a model update
+ synchronized (stateLockObj) {
+ if (!state.equals(AxEngineState.STOPPED)) {
+ throw new ApexException(
+ UPDATE_MODEL + key.getId() + ", cannot update model, engine should be stopped but is in state "
+ + state);
+ }
+ }
+
+ populateIoEventsToTask(apexModel);
+
+ // Create new internal context or update the existing one
+ try {
+ if (internalContext == null) {
+ /// New internal context
+ internalContext = new ApexInternalContext(apexModel);
+ } else {
+ // Existing internal context which must be updated
+ internalContext.update(apexModel, isSubsequentInstance);
+ }
+ } catch (final ContextException e) {
+ throw new ApexException(
+ UPDATE_MODEL + key.getId() + ", error setting the context for engine \"" + key.getId() + "\"", e);
+ }
+
+ // Set up the state machines
+ try {
+ // We always set up state machines as new because it's only context that must be transferred; policies are
+ // always set up as new
+ stateMachineHandler = new StateMachineHandler(internalContext);
+ } catch (final StateMachineException e) {
+ throw new ApexException(
+ UPDATE_MODEL + key.getId() + ", error setting up the engine state machines \"" + key.getId() + "\"", e);
+ }
+
+ LOGGER.exit(UPDATE_MODEL + key.getId());
+ }
+
+
+ private void populateIoEventsToTask(AxPolicyModel apexPolicyModel) {
+ Set<AxArtifactKey> updatedTasks = new TreeSet<>();
+ for (var axPolicy : apexPolicyModel.getPolicies().getPolicyMap().values()) {
+ for (var axState : axPolicy.getStateMap().values()) {
+ AxEvent triggerEvent = apexPolicyModel.getEvents().get(axState.getTrigger());
+ axState.getTaskReferences().forEach((taskKey, taskRef) -> {
+ AxTask task = apexPolicyModel.getTasks().getTaskMap().get(taskKey);
+ task.setInputEvent(triggerEvent);
+ updateTaskBasedOnStateOutput(apexPolicyModel, updatedTasks, axState, taskKey, taskRef, task);
+ updatedTasks.add(taskKey);
+ });
+ }
+ }
+ }
+
+ private void updateTaskBasedOnStateOutput(AxPolicyModel apexPolicyModel, Set<AxArtifactKey> updatedTasks,
+ AxState state, AxArtifactKey taskKey, AxStateTaskReference taskRef, AxTask task) {
+ Map<String, AxEvent> outputEvents = new TreeMap<>();
+ AxStateOutput stateOutput = null;
+ if (AxStateTaskOutputType.LOGIC.equals(taskRef.getStateTaskOutputType())) {
+ // in case of SFL, outgoing event will be same for all state outputs that are part of SFL.So, take any entry
+ stateOutput = state.getStateOutputs().values().iterator().next();
+ } else {
+ stateOutput = state.getStateOutputs().get(taskRef.getOutput().getLocalName());
+ }
+ if (null != stateOutput) {
+ if (null == stateOutput.getOutgoingEventSet() || stateOutput.getOutgoingEventSet().isEmpty()) {
+ Set<AxArtifactKey> outEventSet = new TreeSet<>();
+ outEventSet.add(stateOutput.getOutgoingEvent());
+ stateOutput.setOutgoingEventSet(outEventSet);
+ }
+ if (state.getNextStateSet().isEmpty()
+ || state.getNextStateSet().contains(AxReferenceKey.getNullKey().getLocalName())) {
+ stateOutput.getOutgoingEventSet().forEach(outgoingEventKey -> outputEvents
+ .put(outgoingEventKey.getName(), apexPolicyModel.getEvents().get(outgoingEventKey)));
+ } else {
+ AxArtifactKey outgoingEventKey = stateOutput.getOutgoingEvent();
+ outputEvents.put(outgoingEventKey.getName(), apexPolicyModel.getEvents().get(outgoingEventKey));
+ }
+ if (updatedTasks.contains(taskKey)) {
+ // this happens only when same task is used by multiple policies
+ // with different eventName but same fields
+ task.getOutputEvents().putAll(outputEvents);
+ } else {
+ task.setOutputEvents(outputEvents);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void start() throws ApexException {
+ LOGGER.entry("start() {}", key);
+ synchronized (stateLockObj) {
+ if (state != AxEngineState.STOPPED) {
+ String message =
+ START + key.getId() + "," + state + ", cannot start engine, engine not in state STOPPED";
+ throw new ApexException(message);
+ }
+ }
+
+ if (stateMachineHandler == null || internalContext == null) {
+ throw new ApexException(START + key.getId() + "," + state
+ + ", cannot start engine, engine has not been initialized, its model is not loaded");
+ }
+
+ // Set up the state machines
+ try {
+ // Start the state machines
+ stateMachineHandler.start();
+ engineStats.engineStart();
+ } catch (final StateMachineException e) {
+ String message =
+ UPDATE_MODEL + key.getId() + ", error starting the engine state machines \"" + key.getId() + "\"";
+ throw new ApexException(message, e);
+ }
+
+ // OK, we are good to go
+ state = AxEngineState.READY;
+ updateStatePrometheusMetric();
+
+ LOGGER.exit("start()" + key);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void stop() throws ApexException {
+ LOGGER.entry("stop()-> {}", key);
+
+ // Check if the engine is already stopped
+ synchronized (stateLockObj) {
+ if (state == AxEngineState.STOPPED) {
+ throw new ApexException(
+ STOP + key.getId() + "," + state + ", cannot stop engine, engine is already stopped");
+ }
+ }
+ // Stop the engine if it is in state READY, if it is in state EXECUTING, wait for execution to finish
+ for (int increment = ApexEngineConstants.STOP_EXECUTION_WAIT_TIMEOUT; increment > 0;
+ increment -= ApexEngineConstants.APEX_ENGINE_STOP_EXECUTION_WAIT_INCREMENT) {
+ ThreadUtilities.sleep(ApexEngineConstants.APEX_ENGINE_STOP_EXECUTION_WAIT_INCREMENT);
+
+ synchronized (stateLockObj) {
+ switch (state) {
+ // Engine is OK to stop or has been stopped on return of an event
+ case READY:
+ case STOPPED:
+ state = AxEngineState.STOPPED;
+ updateStatePrometheusMetric();
+ stateMachineHandler.stop();
+ engineStats.engineStop();
+ LOGGER.exit("stop()" + key);
+ return;
+
+ // Engine is executing a policy, wait for it to stop
+ case EXECUTING:
+ state = AxEngineState.STOPPING;
+ updateStatePrometheusMetric();
+ break;
+
+ // Wait for the engine to stop
+ case STOPPING:
+ break;
+
+ default:
+ throw new ApexException(
+ STOP + key.getId() + "," + state + ", cannot stop engine, engine is in an undefined state");
+ }
+ }
+ }
+
+ // Force the engine to STOPPED state
+ synchronized (stateLockObj) {
+ state = AxEngineState.STOPPED;
+ }
+ updateStatePrometheusMetric();
+
+ throw new ApexException(STOP + key.getId() + "," + state + ", error stopping engine, engine stop timed out");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void clear() throws ApexException {
+ LOGGER.entry("clear()-> {}", key);
+ synchronized (stateLockObj) {
+ if (state != AxEngineState.STOPPED) {
+ throw new ApexException(
+ "clear" + "()<-" + key.getId() + "," + state + ", cannot clear engine, engine is not stopped");
+ }
+ }
+
+ // Clear everything
+ stateMachineHandler = null;
+ engineStats.clean();
+
+ if (internalContext != null) {
+ internalContext.clear();
+ internalContext = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public EnEvent createEvent(final AxArtifactKey eventKey) {
+ synchronized (stateLockObj) {
+ if (state != AxEngineState.READY && state != AxEngineState.EXECUTING) {
+ LOGGER.warn("createEvent()<-{},{}, cannot create event, engine not in state READY", key.getId(), state);
+ return null;
+ }
+ }
+
+ try {
+ // Create an event using the internal context
+ return new EnEvent(eventKey);
+ } catch (final Exception e) {
+ LOGGER.warn("createEvent()<-{},{}, error on event creation: ", key.getId(), state, e);
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public boolean handleEvent(final EnEvent incomingEvent) {
+ var ret = false;
+ if (incomingEvent == null) {
+ LOGGER.warn("handleEvent()<-{},{}, cannot run engine, incoming event is null", key.getId(), state);
+ return ret;
+ }
+
+ synchronized (stateLockObj) {
+ if (state != AxEngineState.READY) {
+ LOGGER.warn("handleEvent()<-{},{}, cannot run engine, engine not in state READY", key.getId(), state);
+ return ret;
+ }
+
+ state = AxEngineState.EXECUTING;
+ }
+ updateStatePrometheusMetric();
+
+ String message = "execute(): triggered by event " + incomingEvent.toString();
+ LOGGER.debug(message);
+
+ // By default we return a null event on errors
+ Collection<EnEvent> outgoingEvents = null;
+ try {
+ engineStats.executionEnter(incomingEvent.getKey());
+ outgoingEvents = stateMachineHandler.execute(incomingEvent);
+ engineStats.executionExit();
+ ret = true;
+ } catch (final StateMachineException e) {
+ LOGGER.warn("handleEvent()<-{},{}, engine execution error: ", key.getId(), state, e);
+
+ // Create an exception return event
+ outgoingEvents = createExceptionEvent(incomingEvent, e);
+ }
+
+ // Publish the outgoing event
+ try {
+ synchronized (eventListeners) {
+ if (eventListeners.isEmpty()) {
+ LOGGER.debug("handleEvent()<-{},{}, There is no listener registered to recieve outgoing event: {}",
+ key.getId(), state, outgoingEvents);
+ }
+ for (final EnEventListener axEventListener : eventListeners.values()) {
+ for (var outgoingEvent : outgoingEvents) {
+ axEventListener.onEnEvent(outgoingEvent);
+ }
+ }
+ }
+ } catch (final ApexException e) {
+ LOGGER.warn("handleEvent()<-{},{}, outgoing event publishing error: ", key.getId(), state, e);
+ ret = false;
+ }
+ synchronized (stateLockObj) {
+ // Only go to READY if we are still in state EXECUTING, we go to state STOPPED if we were STOPPING
+ if (state == AxEngineState.EXECUTING) {
+ state = AxEngineState.READY;
+ } else if (state == AxEngineState.STOPPING) {
+ state = AxEngineState.STOPPED;
+ }
+ }
+ updateStatePrometheusMetric();
+ return ret;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void addEventListener(final String listenerName, final EnEventListener listener) {
+ if (listenerName == null) {
+ String message = "addEventListener()<-" + key.getId() + "," + state + ", listenerName is null";
+ throw new ApexRuntimeException(message);
+ }
+
+ if (listener == null) {
+ String message = "addEventListener()<-" + key.getId() + "," + state + ", listener is null";
+ throw new ApexRuntimeException(message);
+ }
+
+ eventListeners.put(listenerName, listener);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void removeEventListener(final String listenerName) {
+ if (listenerName == null) {
+ String message = "removeEventListener()<-" + key.getId() + "," + state + ", listenerName is null";
+ throw new ApexRuntimeException(message);
+ }
+
+ eventListeners.remove(listenerName);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxEngineModel getEngineStatus() {
+ final var engineModel = new AxEngineModel(key);
+ engineModel.setTimestamp(System.currentTimeMillis());
+ engineModel.setState(state);
+ engineModel.setStats(engineStats);
+ return engineModel;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Map<AxArtifactKey, Map<String, Object>> getEngineContext() {
+ final Map<AxArtifactKey, Map<String, Object>> currentContext = new LinkedHashMap<>();
+
+ if (internalContext == null) {
+ return currentContext;
+ }
+
+ for (final Entry<AxArtifactKey, ContextAlbum> contextAlbumEntry : internalContext.getContextAlbums()
+ .entrySet()) {
+ currentContext.put(contextAlbumEntry.getKey(), contextAlbumEntry.getValue());
+ }
+
+ return currentContext;
+ }
+
+ /**
+ * Create an exception event from the incoming event including the exception information on the event.
+ *
+ * @param incomingEvent The incoming event that caused the exception
+ * @param eventException The exception that was thrown
+ * @return the exception event
+ */
+ private Set<EnEvent> createExceptionEvent(final EnEvent incomingEvent, final Exception eventException) {
+ // The exception event is a clone of the incoming event with the exception suffix added to
+ // its name and an extra
+ // field "ExceptionMessage" added
+ final EnEvent exceptionEvent = (EnEvent) incomingEvent.clone();
+
+ // Create the cascaded message string
+ final var exceptionMessageStringBuilder = new StringBuilder();
+ exceptionMessageStringBuilder.append(eventException.getMessage());
+
+ Throwable subException = eventException.getCause();
+ while (subException != null) {
+ exceptionMessageStringBuilder.append("\ncaused by: ");
+ exceptionMessageStringBuilder.append(subException.getMessage());
+ subException = subException.getCause();
+ }
+
+ // Set the exception message on the event
+ exceptionEvent.setExceptionMessage(exceptionMessageStringBuilder.toString());
+
+ return Set.of(exceptionEvent);
+ }
+
+ /**
+ * Update the APEX engine state to prometheus for monitoring.
+ */
+ private void updateStatePrometheusMetric() {
+ ENGINE_STATE.labels(getKey().getId()).set(state.getStateIdentifier());
+ }
+} \ No newline at end of file
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/StateMachineHandler.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/StateMachineHandler.java
new file mode 100644
index 000000000..c173d1f09
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/StateMachineHandler.java
@@ -0,0 +1,188 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.engine.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Set;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.ExecutorFactory;
+import org.onap.policy.apex.core.engine.executor.StateMachineExecutor;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.core.engine.executor.impl.ExecutorFactoryImpl;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicies;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicy;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This handler holds and manages state machines for each policy in an Apex engine. When the class is instantiated, an
+ * executor {@link StateMachineExecutor} is created for each policy in the policy model the state machine handler will
+ * execute. The executors for each policy are held in a map indexed by event.
+ *
+ * <p>When an event is received on the policy, the state machine executor to execute that event is looked up on the
+ * executor map and the event is passed to the executor for execution.
+ *
+ * @author Liam Fallon
+ *
+ */
+public class StateMachineHandler {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(StateMachineHandler.class);
+
+ // The key of the Apex model we are executing
+ private final AxArtifactKey key;
+
+ // The state machines in this engine
+ private final HashMap<AxEvent, StateMachineExecutor> stateMachineExecutorMap = new HashMap<>();
+
+ // The executor factory is used to get logic executors for the particular type of executor we
+ // need for task
+ // selection logic or task logic
+ private final ExecutorFactory executorFactory;
+
+ /**
+ * This constructor builds the state machines for the policies in the apex model.
+ *
+ * @param internalContext The internal context we are using
+ * @throws StateMachineException On state machine initiation errors
+ */
+ protected StateMachineHandler(final ApexInternalContext internalContext) throws StateMachineException {
+ LOGGER.entry("StateMachineHandler()->" + internalContext.getKey().getId());
+
+ key = internalContext.getKey();
+
+ // Create the executor factory to generate executors as the engine runs policies
+ executorFactory = new ExecutorFactoryImpl();
+
+ // Iterate over the policies in the policy model and create a state machine for each one
+ for (final AxPolicy policy : ModelService.getModel(AxPolicies.class).getPolicyMap().values()) {
+ // Create a state machine for this policy
+ final var thisStateMachineExecutor = new StateMachineExecutor(executorFactory, policy.getKey());
+
+ // This executor is the top executor so has no parent
+ thisStateMachineExecutor.setContext(null, policy, internalContext);
+
+ // Get the incoming trigger event
+ final AxEvent triggerEvent = ModelService.getModel(AxEvents.class)
+ .get(policy.getStateMap().get(policy.getFirstState()).getTrigger());
+
+ // Put the state machine executor on the map for this trigger
+ final var lastStateMachineExecutor = stateMachineExecutorMap.put(triggerEvent, thisStateMachineExecutor);
+ if (lastStateMachineExecutor != null
+ && lastStateMachineExecutor.getSubject() != thisStateMachineExecutor.getSubject()) {
+ LOGGER.error("No more than one policy in a model can have the same trigger event. In model "
+ + internalContext.getKey().getId() + " Policy ("
+ + lastStateMachineExecutor.getSubject().getKey().getId() + ") and Policy ("
+ + thisStateMachineExecutor.getSubject().getKey().getId() + ") have the same Trigger event ("
+ + triggerEvent.getKey().getId() + ") ");
+ LOGGER.error(" Policy (" + lastStateMachineExecutor.getSubject().getKey() + ") has overwritten Policy ("
+ + thisStateMachineExecutor.getSubject().getKey().getId()
+ + " so this overwritten policy will never be triggered in this engine.");
+ }
+ }
+
+ LOGGER.exit("StateMachineHandler()<-" + internalContext.getKey().getId());
+ }
+
+ /**
+ * This constructor starts the state machines for each policy, carrying out whatever initialization executors need.
+ *
+ * @throws StateMachineException On state machine initiation errors
+ */
+ protected void start() throws StateMachineException {
+ LOGGER.entry("start()->" + key.getId());
+
+ // Iterate over the state machines
+ for (final StateMachineExecutor smExecutor : stateMachineExecutorMap.values()) {
+ try {
+ smExecutor.prepare();
+ } catch (final StateMachineException e) {
+ final String stateMachineId = smExecutor.getContext().getKey().getId();
+ String message = "start()<-" + key.getId() + ", start failed, state machine \"" + stateMachineId + "\"";
+ LOGGER.warn(message, e);
+ throw new StateMachineException(message, e);
+ }
+ }
+
+ LOGGER.exit("start()<-" + key.getId());
+ }
+
+ /**
+ * This method is called to execute an event on the state machines in an engine.
+ *
+ * @param event The trigger event for the state machine
+ * @return The result of the state machine execution run
+ * @throws StateMachineException On execution errors in a state machine
+ */
+ protected Collection<EnEvent> execute(final EnEvent event) throws StateMachineException {
+ LOGGER.entry("execute()->" + event.getName());
+
+ // Try to execute the state machine for the trigger
+ final var stateMachineExecutor = stateMachineExecutorMap.get(event.getAxEvent());
+ if (stateMachineExecutor == null) {
+ final String exceptionMessage =
+ "state machine execution not possible, policy not found for trigger event " + event.getName();
+ LOGGER.warn(exceptionMessage);
+
+ event.setExceptionMessage(exceptionMessage);
+ return Set.of(event);
+ }
+
+ // Run the state machine
+ try {
+ LOGGER.debug("execute(): state machine \"{}\" execution starting . . .", stateMachineExecutor);
+ final Collection<EnEvent> outputEvents =
+ stateMachineExecutor.execute(event.getExecutionId(), event.getExecutionProperties(), event);
+
+ LOGGER.debug("execute()<-: state machine \"{}\" execution completed", stateMachineExecutor);
+ return outputEvents;
+ } catch (final Exception e) {
+ LOGGER.warn("execute()<-: state machine \"" + stateMachineExecutor + "\" execution failed", e);
+ throw new StateMachineException("execute()<-: execution failed on state machine " + stateMachineExecutor,
+ e);
+ }
+ }
+
+ /**
+ * Closes down the state machines of an engine.
+ */
+ protected void stop() {
+ LOGGER.entry("stop()->");
+
+ // Iterate through all state machines and clean them
+ for (final StateMachineExecutor smExecutor : stateMachineExecutorMap.values()) {
+ try {
+ smExecutor.cleanUp();
+ } catch (final StateMachineException e) {
+ final String smId = smExecutor.getContext().getKey().getId();
+ LOGGER.warn("stop()<-clean up failed, state machine \"" + smId + "\" cleanup failed", e);
+ }
+ }
+ LOGGER.exit("stop()<-");
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/package-info.java
new file mode 100644
index 000000000..d8e1329e7
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/impl/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides the implementation of the {@link org.onap.policy.apex.core.engine.engine.ApexEngine}
+ * interface.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.engine.impl;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/engine/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/engine/package-info.java
new file mode 100644
index 000000000..ac5224d31
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/engine/package-info.java
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Defines the Apex engine Java API. The API is used to set up, control, send events to, and receive events from an APEX
+ * engine. The ApexEngine interface is used to control the execution of a single APEX engine thread and to send
+ * events to that APEX engine thread. The EnEventListener interface is used to listen for events being emitted
+ * by an APEX engine thread.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.engine;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/event/EnEvent.java b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnEvent.java
new file mode 100644
index 000000000..29802e06d
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnEvent.java
@@ -0,0 +1,291 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.event;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.apex.core.engine.monitoring.EventMonitor;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * Instances of the Class EnEvent are events being passed through the Apex system. All events in the
+ * system are instances of this class.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
+public class EnEvent extends HashMap<String, Object> {
+ private static final long serialVersionUID = 6311863111866294637L;
+
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(EnEvent.class);
+
+ // Repeasted string constants
+ private static final String NULL_KEYS_ILLEGAL = "null keys are illegal on method parameter \"key\"";
+
+ /*
+ * This is not used for encryption/security, thus disabling sonar.
+ */
+ private static Random rand = new Random(System.nanoTime()); // NOSONAR
+
+ // The definition of this event in the Apex model
+ @Setter(AccessLevel.NONE)
+ @EqualsAndHashCode.Include
+ private final AxEvent axEvent;
+
+ // The event monitor for this event
+ @Getter(AccessLevel.NONE)
+ private final transient EventMonitor eventMonitor = new EventMonitor();
+
+ // The stack of execution of this event, used for monitoring
+ private AxConcept[] userArtifactStack;
+
+ // An identifier for the current event execution. The default value here will always be a random
+ // number, and should be reset
+ private long executionId = rand.nextLong();
+
+ // Event related properties used during processing of this event
+ private Properties executionProperties = new Properties();
+
+ // A string holding a message that indicates why processing of this event threw an exception
+ private String exceptionMessage;
+
+ /**
+ * Instantiates a new EnEvent, an Engine Event.
+ *
+ * @param eventKey the key of the event definition from the Apex model
+ */
+ public EnEvent(final AxArtifactKey eventKey) {
+ this(ModelService.getModel(AxEvents.class).get(eventKey));
+ }
+
+ /**
+ * Instantiates a new EnEvent, an Engine Event.
+ *
+ * @param axEvent the event definition from the Apex model
+ */
+ public EnEvent(final AxEvent axEvent) {
+ super();
+
+ if (axEvent == null) {
+ throw new EnException("event definition is null or was not found in model service");
+ }
+ // Save the event definition from the Apex model
+ this.axEvent = axEvent;
+ }
+
+ /**
+ * Get the name of the event.
+ *
+ * @return the event name
+ */
+ public String getName() {
+ return axEvent.getKey().getName();
+ }
+
+ /**
+ * Get the key of the event.
+ *
+ * @return the event key
+ */
+ public AxArtifactKey getKey() {
+ return axEvent.getKey();
+ }
+
+ /**
+ * Get the ID of the event.
+ *
+ * @return the event key
+ */
+ public String getId() {
+ return axEvent.getKey().getId();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Object get(final Object key) {
+ if (key == null) {
+ LOGGER.warn("null values are illegal on method parameter \"key\"");
+ throw new EnException("null values are illegal on method parameter \"key\"");
+ }
+
+ // Check if this key is a parameter on our event
+ final AxField eventParameter = axEvent.getParameterMap().get(key);
+ if (eventParameter == null) {
+ String message = "parameter with key " + key + " not defined on this event";
+ LOGGER.warn(message);
+ throw new EnException(message);
+ }
+
+ // Get the item
+ final Object item = super.get(key);
+
+ // Get the parameter value and monitor it
+ eventMonitor.monitorGet(eventParameter, item, userArtifactStack);
+ return item;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Collection<Object> values() {
+ // Build the key set and return it
+ final ArrayList<Object> valueList = new ArrayList<>();
+
+ // Override the generic "values()" call as we want to monitor the gets
+ for (final String key : super.keySet()) {
+ valueList.add(this.get(key));
+ }
+
+ return valueList;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Set<Map.Entry<String, Object>> entrySet() {
+ // Build the entry set and return it
+ final Set<Map.Entry<String, Object>> entrySet = new HashSet<>();
+
+ // Override the generic "entrySet()" call as we want to monitor the gets
+ for (final String key : super.keySet()) {
+ entrySet.add(new SimpleEntry<>(key, this.get(key)));
+ }
+
+ return entrySet;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Object put(final String key, final Object incomingValue) {
+ if (key == null) {
+ String message = NULL_KEYS_ILLEGAL;
+ LOGGER.warn(message);
+ throw new EnException(message);
+ }
+
+ // Check if this key is a parameter on our event
+ final AxField eventParameter = axEvent.getParameterMap().get(key);
+ if (eventParameter == null) {
+ String message = "parameter with key \"" + key + "\" not defined on event \"" + getName() + "\"";
+ LOGGER.warn(message);
+ throw new EnException(message);
+ }
+
+ // We allow null values
+ if (incomingValue == null) {
+ eventMonitor.monitorSet(eventParameter, incomingValue, userArtifactStack);
+ return super.put(key, incomingValue);
+ }
+
+ // Holder for the object to assign
+ final Object valueToAssign = new EnField(eventParameter, incomingValue).getAssignableValue();
+
+ // Update the value in the parameter map
+ eventMonitor.monitorSet(eventParameter, valueToAssign, userArtifactStack);
+ return super.put(key, valueToAssign);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void putAll(final Map<? extends String, ? extends Object> incomingMap) {
+ // Override the generic "putAll()" call as we want to monitor the puts
+ for (final Map.Entry<? extends String, ? extends Object> incomingEntry : incomingMap.entrySet()) {
+ put(incomingEntry.getKey(), incomingEntry.getValue());
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Object remove(final Object key) {
+ if (key == null) {
+ LOGGER.warn(NULL_KEYS_ILLEGAL);
+ throw new EnException(NULL_KEYS_ILLEGAL);
+ }
+
+ // Check if this key is a parameter on our event
+ final AxField eventParameter = axEvent.getParameterMap().get(key);
+ if (eventParameter == null) {
+ String message = "parameter with key " + key + " not defined on this event";
+ LOGGER.warn(message);
+ throw new EnException(message);
+ }
+
+ final Object removedValue = super.remove(key);
+ eventMonitor.monitorRemove(eventParameter, removedValue, userArtifactStack);
+ return removedValue;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void clear() {
+ // Override the generic "clear()" call as we want to monitor removals
+ final Set<String> deleteSet = new HashSet<>();
+ deleteSet.addAll(keySet());
+
+ for (final String deleteKey : deleteSet) {
+ this.remove(deleteKey);
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "EnEvent [axEvent=" + axEvent + ", userArtifactStack=" + Arrays.toString(userArtifactStack) + ", map="
+ + super.toString() + "]";
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/event/EnException.java b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnException.java
new file mode 100644
index 000000000..d9520336b
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnException.java
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.event;
+
+import org.onap.policy.apex.model.basicmodel.concepts.ApexRuntimeException;
+
+/**
+ * This class will be called if an error occurs in Apex event handling.
+ *
+ * @author Liam Fallon
+ */
+public class EnException extends ApexRuntimeException {
+ private static final long serialVersionUID = -8507246953751956974L;
+
+ /**
+ * Instantiates a new engine event exception.
+ *
+ * @param message the message
+ */
+ public EnException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Instantiates a new engine event exception.
+ *
+ * @param message the message
+ * @param ex the exception
+ */
+ public EnException(final String message, final Exception ex) {
+ super(message, ex);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/event/EnField.java b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnField.java
new file mode 100644
index 000000000..99a95cae1
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/event/EnField.java
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.event;
+
+import java.io.Serializable;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextRuntimeException;
+import org.onap.policy.apex.context.SchemaHelper;
+import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * Instances of the Class EnField are event fields being passed through the Apex system.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@Getter
+public class EnField implements Serializable {
+ private static final long serialVersionUID = -5713525780081840333L;
+
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(EnField.class);
+
+ // The definition of this field in the Apex model
+ private final AxField axField;
+
+ // The schema helper for this field
+ private transient SchemaHelper schemaHelper;
+
+ // The value of this field
+ private final transient Object value;
+
+ /**
+ * Instantiates a new EnField, an Engine Field.
+ *
+ * @param axField the field definition from the Apex model
+ * @param value the value
+ */
+ public EnField(final AxField axField, final Object value) {
+ // Save the field definition from the Apex model
+ this.axField = axField;
+ this.value = value;
+
+ // Get a schema helper to handle translations of fields to and from the schema
+ try {
+ schemaHelper = new SchemaHelperFactory().createSchemaHelper(axField.getKey(), axField.getSchema());
+ } catch (final ContextRuntimeException e) {
+ final String message = "schema helper cannot be created for parameter with key \"" + axField.getId()
+ + "\" with schema \"" + axField.getSchema() + "\"";
+ LOGGER.warn(message, e);
+ throw new EnException(message, e);
+ }
+ }
+
+ /**
+ * Get the name of the field.
+ *
+ * @return the field name
+ */
+ public String getName() {
+ return axField.getKey().getLocalName();
+ }
+
+ /**
+ * Get the key of the field.
+ *
+ * @return the field key
+ */
+ public AxReferenceKey getKey() {
+ return axField.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "EnField [axField=" + axField + ", value=" + value + "]";
+ }
+
+ /**
+ * Get an assignable object that will work with the field.
+ *
+ * @return the assignable value
+ */
+ public Object getAssignableValue() {
+ // Use the schema helper to get the translated value of the object
+ return schemaHelper.unmarshal(value);
+ }
+
+ /**
+ * Is the value object assignable to this field.
+ *
+ * @return true if the value is assignable
+ */
+ public boolean isAssignableValue() {
+ try {
+ schemaHelper.unmarshal(value);
+ return true;
+ } catch (final Exception e) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("value {} is not assignable to this field", value, e);
+ }
+ return false;
+ }
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/event/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/event/package-info.java
new file mode 100644
index 000000000..23d51a41a
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/event/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides the event handling classes that an APEX engine uses and which uses use to send and
+ * receive events to and from an APEX engine.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.event;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/Executor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/Executor.java
new file mode 100644
index 000000000..7ebed1d49
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/Executor.java
@@ -0,0 +1,167 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import java.util.Properties;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+
+/**
+ * This interface defines what operations must be provided by an executing entity in Apex. It is
+ * implemented by classes that execute logic in a state machine. Each executor has an incoming
+ * entity {@code IN} that triggers execution, an outgoing entity {@code OUT} that is produced by
+ * execution, a subject {@code SUBJECT} that is being executed, and a context {@code CONTEXT} in
+ * which execution is being carried out. An executor can be part of a chain of executors and the
+ * {@code setNext} method is used to set the next executor to be executed after this executor has
+ * completed.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ *
+ * @param <I> type of the incoming entity
+ * @param <O> type of the outgoing entity
+ * @param <S> type that is the subject of execution
+ * @param <C> context holding the context of execution
+ */
+
+public interface Executor<I, O, S, C> {
+ /**
+ * Save the subject and context of the executor.
+ *
+ * @param parent the parent executor of this executor or null if this executor is the top
+ * executor
+ * @param executorSubject the executor subject, the subject of execution
+ * @param executorContext the executor context, the context in which execution takes place
+ */
+ void setContext(Executor<?, ?, ?, ?> parent, S executorSubject, C executorContext);
+
+ /**
+ * Prepares the processing.
+ *
+ * @throws StateMachineException thrown when a state machine execution error occurs
+ */
+ void prepare() throws StateMachineException;
+
+ /**
+ * Executes the executor, running through its context in its natural order.
+ *
+ * @param executionId the execution ID of the current APEX execution policy thread
+ * @param executionProperties the execution properties to set
+ * @param incomingEntity the incoming entity that triggers execution
+ * @return The outgoing entity that is the result of execution
+ * @throws ApexException on an execution error
+ */
+ O execute(long executionId, Properties executionProperties, I incomingEntity) throws ApexException;
+
+ /**
+ * Carry out the preparatory work for execution.
+ *
+ * @param executionId the execution ID of the current APEX execution policy thread
+ * @param executionProperties the execution properties to set
+ * @param incomingEntity the incoming entity that triggers execution
+ * @throws ApexException on an execution error
+ */
+ void executePre(long executionId, Properties executionProperties, I incomingEntity) throws ApexException;
+
+ /**
+ * Carry out the post work for execution, the returning entity should be set by the child
+ * execution object.
+ *
+ * @param returnValue the return value indicates whether the execution was successful and, if it
+ * failed, how it failed
+ * @throws ApexException on an execution error
+ */
+ void executePost(boolean returnValue) throws ApexException;
+
+ /**
+ * Cleans up after processing.
+ *
+ * @throws StateMachineException thrown when a state machine execution error occurs
+ */
+ void cleanUp() throws StateMachineException;
+
+ /**
+ * Get the key associated with the executor.
+ *
+ * @return The key associated with the executor
+ */
+ AxConcept getKey();
+
+ /**
+ * Get the parent executor of the executor.
+ *
+ * @return The parent executor of this executor
+ */
+ @SuppressWarnings("rawtypes")
+ Executor getParent();
+
+ /**
+ * Get the subject of the executor.
+ *
+ * @return The subject for the executor
+ */
+ S getSubject();
+
+ /**
+ * Get the context of the executor.
+ *
+ * @return The context for the executor
+ */
+ C getContext();
+
+ /**
+ * Get the incoming object of the executor.
+ *
+ * @return The incoming object for the executor
+ */
+ I getIncoming();
+
+ /**
+ * Get the outgoing object of the executor.
+ *
+ * @return The outgoing object for the executor
+ */
+ O getOutgoing();
+
+ /**
+ * Save the next executor for this executor.
+ *
+ * @param nextExecutor the next executor
+ */
+ void setNext(Executor<I, O, S, C> nextExecutor);
+
+ /**
+ * Get the next executor to be run after this executor completes its execution.
+ *
+ * @return The next executor
+ */
+ Executor<I, O, S, C> getNext();
+
+ /**
+ * Set parameters for this executor, overloaded by executors that use parameters.
+ *
+ * @param parameters executor parameters
+ */
+ void setParameters(ExecutorParameters parameters);
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/ExecutorFactory.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/ExecutorFactory.java
new file mode 100644
index 000000000..fb6c7b45e
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/ExecutorFactory.java
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateFinalizerLogic;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+
+/**
+ * This class is used by the state machine to get implementations of task selection and task
+ * executors.
+ *
+ * @author Liam Fallon
+ */
+
+public interface ExecutorFactory {
+ /**
+ * Get an executor for task selection logic.
+ *
+ * @param stateExecutor the state executor that is requesting the task selection executor
+ * @param state the state containing the task selection logic
+ * @param context the context the context in which the task selection logic will execute
+ * @return The executor that will run the task selection logic
+ */
+ public abstract TaskSelectExecutor getTaskSelectionExecutor(Executor<?, ?, ?, ?> stateExecutor, AxState state,
+ ApexInternalContext context);
+
+ /**
+ * Get an executor for task logic.
+ *
+ * @param stateExecutor the state executor that is requesting the task executor
+ * @param task the task containing the task logic
+ * @param context the context the context in which the task logic will execute
+ * @return The executor that will run the task logic
+ */
+ public abstract TaskExecutor getTaskExecutor(Executor<?, ?, ?, ?> stateExecutor, AxTask task,
+ ApexInternalContext context);
+
+ /**
+ * Get an executor for state finalizer logic.
+ *
+ * @param stateExecutor the state executor that is requesting the state finalizer executor
+ * @param logic the state finalizer logic to execute
+ * @param context the context the context in which the state finalizer logic will execute
+ * @return The executor that will run the state finalizer logic
+ */
+ public abstract StateFinalizerExecutor getStateFinalizerExecutor(Executor<?, ?, ?, ?> stateExecutor,
+ AxStateFinalizerLogic logic, ApexInternalContext context);
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateExecutor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateExecutor.java
new file mode 100644
index 000000000..5fb51ca70
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateExecutor.java
@@ -0,0 +1,357 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineRuntimeException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateFinalizerLogic;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateOutput;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateTaskOutputType;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateTaskReference;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.onap.policy.apex.model.policymodel.concepts.AxTasks;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This class is the executor for a state of a policy.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class StateExecutor implements Executor<EnEvent, StateOutput, AxState, ApexInternalContext> {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(StateExecutor.class);
+
+ // Hold the state and context definitions for this state
+ private AxState axState = null;
+ @Getter
+ private Executor<?, ?, ?, ?> parent = null;
+ private ApexInternalContext context = null;
+
+ // Holds the incoming event and the state output for this state
+ private EnEvent lastIncomingEvent = null;
+ private StateOutput lastStateOutput = null;
+
+ // The task selection logic executor
+ private TaskSelectExecutor taskSelectExecutor = null;
+
+ // The map of task executors for this state
+ private final Map<AxArtifactKey, TaskExecutor> taskExecutorMap = new HashMap<>();
+
+ // The map of state outputs used directly by tasks
+ private final Map<AxArtifactKey, String> directStateOutputMap = new HashMap<>();
+
+ // The map of state finalizer logic executors used by tasks
+ private final Map<AxArtifactKey, StateFinalizerExecutor> task2StateFinalizerMap = new HashMap<>();
+
+ // The next state executor
+ private Executor<EnEvent, StateOutput, AxState, ApexInternalContext> nextExecutor = null;
+
+ // The executor factory
+ private ExecutorFactory executorFactory = null;
+
+ /**
+ * Constructor, save the executor factory.
+ *
+ * @param executorFactory the executor factory to use for getting executors for task selection logic
+ */
+ public StateExecutor(final ExecutorFactory executorFactory) {
+ this.executorFactory = executorFactory;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setContext(final Executor<?, ?, ?, ?> incomingParent, final AxState incomingAxState,
+ final ApexInternalContext incomingContext) {
+ // Save the state and context definition
+ this.parent = incomingParent;
+ this.axState = incomingAxState;
+ this.context = incomingContext;
+
+ // Set the task selection executor
+ taskSelectExecutor = executorFactory.getTaskSelectionExecutor(this, axState, context);
+
+ // Set a task executor for each task
+ for (final Entry<AxArtifactKey, AxStateTaskReference> stateTaskReferenceEntry : axState.getTaskReferences()
+ .entrySet()) {
+ final AxArtifactKey taskKey = stateTaskReferenceEntry.getKey();
+ final AxStateTaskReference taskReference = stateTaskReferenceEntry.getValue();
+
+ // Get the task
+ final AxTask task = ModelService.getModel(AxTasks.class).get(taskKey);
+
+ // Create a task executor for the task
+ taskExecutorMap.put(taskKey, executorFactory.getTaskExecutor(this, task, context));
+
+ // Check what type of output is specified for the task on this sate
+ if (taskReference.getStateTaskOutputType().equals(AxStateTaskOutputType.DIRECT)) {
+ // Create a task state output reference for this task
+ directStateOutputMap.put(taskKey, taskReference.getOutput().getLocalName());
+ } else if (taskReference.getStateTaskOutputType().equals(AxStateTaskOutputType.LOGIC)) {
+ // Get the state finalizer logic for this task
+ final AxStateFinalizerLogic finalizerLogic =
+ axState.getStateFinalizerLogicMap().get(taskReference.getOutput().getLocalName());
+ if (finalizerLogic == null) {
+ // Finalizer logic for the task does not exist
+ throw new StateMachineRuntimeException("state finalizer logic on task reference \"" + taskReference
+ + "\" on state \"" + axState.getId() + "\" does not exist");
+ }
+
+ // Create a state finalizer executor for the task
+ task2StateFinalizerMap.put(taskKey,
+ executorFactory.getStateFinalizerExecutor(this, finalizerLogic, context));
+ } else {
+ // This should never happen but.....
+ throw new StateMachineRuntimeException("invalid state output type on task reference \"" + taskReference
+ + "\" on state \"" + axState.getId() + "\"");
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void prepare() throws StateMachineException {
+ // There may be no task selection logic
+ if (taskSelectExecutor != null) {
+ // Prepare the task selector
+ taskSelectExecutor.prepare();
+ }
+
+ // Prepare the tasks
+ for (final TaskExecutor taskExecutor : taskExecutorMap.values()) {
+ taskExecutor.prepare();
+ }
+
+ for (final StateFinalizerExecutor stateFinalizer : task2StateFinalizerMap.values()) {
+ stateFinalizer.prepare();
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public StateOutput execute(final long executionId, final Properties executionProperties,
+ final EnEvent incomingEvent) throws StateMachineException, ContextException {
+ this.lastIncomingEvent = incomingEvent;
+
+ // Check that the incoming event matches the trigger for this state
+ if (!incomingEvent.getAxEvent().getKey().equals(axState.getTrigger())) {
+ throw new StateMachineException("incoming event \"" + incomingEvent.getId() + "\" does not match trigger \""
+ + axState.getTrigger().getId() + "\" of state \"" + axState.getId() + "\"");
+ }
+
+ // The key of the task to execute
+ AxArtifactKey taskKey = null;
+
+ try {
+ // There may be no task selection logic, in which case just return the default task
+ if (taskSelectExecutor != null) {
+ // Fire the task selector to find the task to run
+ taskKey = taskSelectExecutor.execute(executionId, executionProperties, incomingEvent);
+ }
+
+ // If there's no task selection logic or the TSL returned no task, just use the default
+ // task
+ if (taskKey == null) {
+ taskKey = axState.getDefaultTask();
+ }
+
+ // Execute the task
+ final TreeMap<String, Object> incomingValues = new TreeMap<>();
+ incomingValues.putAll(incomingEvent);
+ final Map<String, Map<String, Object>> taskExecutionResultMap =
+ taskExecutorMap.get(taskKey).execute(executionId, executionProperties, incomingValues);
+ final AxTask task = taskExecutorMap.get(taskKey).getSubject();
+
+ // Check if this task has direct output
+ String stateOutputName = directStateOutputMap.get(taskKey);
+
+ // If a direct state output name was not found, state finalizer logic should be defined
+ // for the task
+ if (stateOutputName == null) {
+ // State finalizer logic should exist for the task
+ final StateFinalizerExecutor finalizerLogicExecutor = task2StateFinalizerMap.get(taskKey);
+ if (finalizerLogicExecutor == null) {
+ throw new StateMachineException("state finalizer logic for task \"" + taskKey.getId()
+ + "\" not found for state \"" + axState.getId() + "\"");
+ }
+
+ // Execute the state finalizer logic to select a state output and to adjust the
+ // taskExecutionResultMap
+ // Multiple event outputs are possible only from final state, otherwise there will be only 1 outputevent
+ stateOutputName = finalizerLogicExecutor.execute(executionId, executionProperties,
+ taskExecutionResultMap.values().iterator().next());
+ }
+
+ // Now look up the the actual state output
+ final AxStateOutput stateOutputDefinition = axState.getStateOutputs().get(stateOutputName);
+ if (stateOutputDefinition == null) {
+ throw new StateMachineException("state output definition for state output \"" + stateOutputName
+ + "\" not found for state \"" + axState.getId() + "\"");
+ }
+
+ // Create the state output and transfer all the fields across to its event
+ final var stateOutput = new StateOutput(stateOutputDefinition);
+ this.lastStateOutput = stateOutput;
+
+ stateOutput.setEventFields(task.getOutputEvents(), taskExecutionResultMap);
+
+ // Copy across fields from the incoming event that are not set on the outgoing event
+ stateOutput.copyUnsetFields(incomingEvent);
+
+ // Set the ExecutionID for the outgoing event to the value in the incoming event.
+ stateOutput.getOutputEvents().values().forEach(outputEvent -> {
+ outputEvent.setExecutionId(incomingEvent.getExecutionId());
+ outputEvent.setExecutionProperties(incomingEvent.getExecutionProperties());
+ });
+
+ // That's it, the state execution is complete
+ return stateOutput;
+ } catch (final Exception e) {
+ final String errorMessage = "State execution of state \"" + axState.getId() + "\" on task \""
+ + (taskKey != null ? taskKey.getId() : "null") + "\" failed: " + e.getMessage();
+
+ LOGGER.warn(errorMessage);
+ throw new StateMachineException(errorMessage, e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePre(final long executionId, final Properties executionProperties,
+ final EnEvent incomingEntity) throws StateMachineException {
+ throw new StateMachineException("execution pre work not implemented on class");
+ }
+
+ @Override
+ public final void executePost(final boolean returnValue) throws StateMachineException {
+ throw new StateMachineException("execution post work not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void cleanUp() throws StateMachineException {
+ // Clean the tasks
+ for (final TaskExecutor taskExecutor : taskExecutorMap.values()) {
+ taskExecutor.cleanUp();
+ }
+
+ if (taskSelectExecutor != null) {
+ // Clean the task selector
+ taskSelectExecutor.cleanUp();
+ }
+
+ for (final StateFinalizerExecutor stateFinalizer : task2StateFinalizerMap.values()) {
+ stateFinalizer.cleanUp();
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxReferenceKey getKey() {
+ return axState.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxState getSubject() {
+ return axState;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final ApexInternalContext getContext() {
+ return context;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final EnEvent getIncoming() {
+ return lastIncomingEvent;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final StateOutput getOutgoing() {
+ return lastStateOutput;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void setNext(final Executor<EnEvent, StateOutput, AxState, ApexInternalContext> incomingNextExecutor) {
+ this.nextExecutor = incomingNextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final Executor<EnEvent, StateOutput, AxState, ApexInternalContext> getNext() {
+ return nextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParameters(final ExecutorParameters parameters) {
+ // Not implemented in this class
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutor.java
new file mode 100644
index 000000000..f490a9849
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutor.java
@@ -0,0 +1,236 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import static org.onap.policy.common.utils.validation.Assertions.argumentOfClassNotNull;
+
+import java.util.Map;
+import java.util.Properties;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.executor.context.StateFinalizerExecutionContext;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateFinalizerLogic;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This abstract class executes state finalizer logic in a state of an Apex policy and is specialized by classes that
+ * implement execution of state finalizer logic.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public abstract class StateFinalizerExecutor
+ implements Executor<Map<String, Object>, String, AxStateFinalizerLogic, ApexInternalContext> {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(StateFinalizerExecutor.class);
+
+ // Repeated string constants
+ private static final String EXECUTE_POST_SFL = "execute-post: state finalizer logic \"";
+
+ // Hold the state and context definitions
+ @Getter
+ private Executor<?, ?, ?, ?> parent = null;
+ private AxState axState = null;
+ private AxStateFinalizerLogic finalizerLogic = null;
+ private ApexInternalContext internalContext = null;
+
+ // Holds the incoming and outgoing fields
+ private Map<String, Object> incomingFields = null;
+
+ // The next state finalizer executor
+ private Executor<Map<String, Object>, String, AxStateFinalizerLogic, ApexInternalContext> nextExecutor = null;
+
+ // The execution context; contains the facades for events and context to be used by tasks
+ // executed by this task
+ // executor
+ @Getter(AccessLevel.PROTECTED)
+ private StateFinalizerExecutionContext executionContext = null;
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setContext(final Executor<?, ?, ?, ?> incomingParent,
+ final AxStateFinalizerLogic incomingFinalizerLogic, final ApexInternalContext incomingInternalContext) {
+ this.parent = incomingParent;
+ axState = (AxState) parent.getSubject();
+ this.finalizerLogic = incomingFinalizerLogic;
+ this.internalContext = incomingInternalContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void prepare() throws StateMachineException {
+ LOGGER.debug("prepare:" + finalizerLogic.getId() + "," + finalizerLogic.getLogicFlavour() + ","
+ + finalizerLogic.getLogic());
+ argumentOfClassNotNull(finalizerLogic.getLogic(), StateMachineException.class,
+ "state finalizer logic cannot be null.");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String execute(final long executionId, final Properties executionProperties,
+ final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException {
+ throw new StateMachineException("execute() not implemented on abstract StateFinalizerExecutionContext class, "
+ + "only on its subclasses");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePre(final long executionId, @NonNull final Properties executionProperties,
+ final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException {
+ LOGGER.debug("execute-pre:" + finalizerLogic.getLogicFlavour() + "," + getSubject().getId() + ","
+ + finalizerLogic.getLogic());
+
+ // Record the incoming fields
+ this.incomingFields = newIncomingFields;
+
+ // Get state finalizer context object
+ executionContext = new StateFinalizerExecutionContext(this, executionId, executionProperties, axState,
+ getIncoming(), axState.getStateOutputs().keySet(), getContext());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePost(final boolean returnValue) throws StateMachineException, ContextException {
+ if (!returnValue) {
+ String errorMessage = "execute-post: state finalizer logic execution failure on state \"" + axState.getId()
+ + "\" on finalizer logic " + finalizerLogic.getId();
+ if (executionContext.getMessage() != null) {
+ errorMessage += ", user message: " + executionContext.getMessage();
+ }
+ LOGGER.warn(errorMessage);
+ throw new StateMachineException(errorMessage);
+ }
+
+ // Check a state output has been selected
+ if (getOutgoing() == null) {
+ String message = EXECUTE_POST_SFL + finalizerLogic.getId() + "\" did not select an output state";
+ LOGGER.warn(message);
+ throw new StateMachineException(message);
+ }
+
+ if (!axState.getStateOutputs().keySet().contains(getOutgoing())) {
+ LOGGER.warn(EXECUTE_POST_SFL + finalizerLogic.getId() + "\" selected output state \"" + getOutgoing()
+ + "\" that does not exsist on state \"" + axState.getId() + "\"");
+ throw new StateMachineException(EXECUTE_POST_SFL + finalizerLogic.getId() + "\" selected output state \""
+ + getOutgoing() + "\" that does not exsist on state \"" + axState.getId() + "\"");
+ }
+
+ LOGGER.debug("execute-post:{}, returning state output \"{}\" and fields {}", finalizerLogic.getId(),
+ getOutgoing(), incomingFields);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void cleanUp() throws StateMachineException {
+ throw new StateMachineException("cleanUp() not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxReferenceKey getKey() {
+ return finalizerLogic.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxStateFinalizerLogic getSubject() {
+ return finalizerLogic;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ApexInternalContext getContext() {
+ return internalContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Map<String, Object> getIncoming() {
+ return incomingFields;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String getOutgoing() {
+ if (executionContext != null) {
+ return executionContext.getSelectedStateOutputName();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setNext(
+ final Executor<Map<String, Object>, String, AxStateFinalizerLogic, ApexInternalContext> inNextEx) {
+ this.nextExecutor = inNextEx;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Executor<Map<String, Object>, String, AxStateFinalizerLogic, ApexInternalContext> getNext() {
+ return nextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParameters(final ExecutorParameters parameters) {
+ // Not used
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutor.java
new file mode 100644
index 000000000..6cbc04678
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutor.java
@@ -0,0 +1,260 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.policymodel.concepts.AxPolicy;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateOutput;
+
+/**
+ * This class is the executor for a state machine built from a policy.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class StateMachineExecutor implements Executor<EnEvent, Collection<EnEvent>, AxPolicy, ApexInternalContext> {
+ // The Apex Policy and context for this state machine
+ private AxPolicy axPolicy = null;
+ @Getter
+ private Executor<?, ?, ?, ?> parent = null;
+ private ApexInternalContext internalContext = null;
+
+ // The list of state executors for this state machine
+ private final Map<AxReferenceKey, StateExecutor> stateExecutorMap = new TreeMap<>();
+
+ // The first executor
+ private StateExecutor firstExecutor = null;
+
+ // The next state machine executor
+ private Executor<EnEvent, Collection<EnEvent>, AxPolicy, ApexInternalContext> nextExecutor = null;
+
+ // The executor factory
+ private ExecutorFactory executorFactory = null;
+
+ /**
+ * Constructor, save the executor factory that will give us executors for task selection logic and task logic.
+ *
+ * @param executorFactory the executor factory
+ * @param owner the artifact key of the owner of this state machine
+ */
+ public StateMachineExecutor(final ExecutorFactory executorFactory, final AxArtifactKey owner) {
+ this.executorFactory = executorFactory;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setContext(final Executor<?, ?, ?, ?> newParent, final AxPolicy newAxPolicy,
+ final ApexInternalContext newInternalContext) {
+ // Save the policy and context for this state machine
+ this.parent = newParent;
+ this.axPolicy = newAxPolicy;
+ this.internalContext = newInternalContext;
+
+ // Clear the first executor, setContext can be called multiple times
+ firstExecutor = null;
+
+ // Create the state executors for this state machine
+ StateExecutor lastExecutor = null;
+ for (final AxState state : axPolicy.getStateMap().values()) {
+ // Create a state executor for this state and add its context (the state)
+ final var stateExecutor = new StateExecutor(executorFactory);
+ stateExecutor.setContext(this, state, internalContext);
+
+ // Update the next executor on the last executor
+ if (lastExecutor != null) {
+ lastExecutor.setNext(stateExecutor);
+ }
+ lastExecutor = stateExecutor;
+
+ // Add the state executor to the executor list
+ stateExecutorMap.put(state.getKey(), stateExecutor);
+
+ // Set the first executor if it is not set
+ if (state.getKey().getLocalName().equals(axPolicy.getFirstState())) {
+ firstExecutor = stateExecutor;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void prepare() throws StateMachineException {
+ for (final StateExecutor stateExecutor : stateExecutorMap.values()) {
+ stateExecutor.prepare();
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Collection<EnEvent> execute(final long executionId, final Properties executionProperties,
+ final EnEvent incomingEvent) throws StateMachineException, ContextException {
+ // Check if there are any states on the state machine
+ if (stateExecutorMap.size() == 0) {
+ throw new StateMachineException("no states defined on state machine");
+ }
+
+ // Check if the first state of the machine is defined
+ if (firstExecutor == null) {
+ throw new StateMachineException("first state not defined on state machine");
+ }
+
+ // Get the first state of the state machine and define a state output that starts state
+ // execution
+ var stateExecutor = firstExecutor;
+ var stateOutput = new StateOutput(new AxStateOutput(firstExecutor.getSubject().getKey(),
+ incomingEvent.getKey(), firstExecutor.getSubject().getKey()), incomingEvent);
+
+ while (true) {
+ // OutputEventSet in a stateoutput can contain multiple events only when it is of the final state
+ // otherwise, there can be only 1 item in outputEventSet
+ stateOutput = stateExecutor.execute(executionId, executionProperties,
+ stateOutput.getOutputEvents().values().iterator().next());
+
+ // Use the next state of the state output to find if all the states have executed
+ if (stateOutput.getNextState().equals(AxReferenceKey.getNullKey())) {
+ break;
+ }
+
+ // Use the next state of the state output to find the next state
+ stateExecutor = stateExecutorMap.get(stateOutput.getNextState());
+ if (stateExecutor == null) {
+ throw new StateMachineException(
+ "state execution failed, next state \"" + stateOutput.getNextState().getId() + "\" not found");
+ }
+ }
+
+ return stateOutput.getOutputEvents().values();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePre(final long executionId, final Properties executionProperties,
+ final EnEvent incomingEntity) throws StateMachineException {
+ throw new StateMachineException("execution pre work not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePost(final boolean returnValue) throws StateMachineException {
+ throw new StateMachineException("execution post work not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void cleanUp() throws StateMachineException {
+ for (final StateExecutor stateExecutor : stateExecutorMap.values()) {
+ stateExecutor.cleanUp();
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxArtifactKey getKey() {
+ return axPolicy.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final AxPolicy getSubject() {
+ return axPolicy;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final ApexInternalContext getContext() {
+ return internalContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final EnEvent getIncoming() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final Collection<EnEvent> getOutgoing() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void setNext(
+ final Executor<EnEvent, Collection<EnEvent>, AxPolicy, ApexInternalContext> newNextExecutor) {
+ this.nextExecutor = newNextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final Executor<EnEvent, Collection<EnEvent>, AxPolicy, ApexInternalContext> getNext() {
+ return nextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParameters(final ExecutorParameters parameters) {
+ // Not implemented in this class
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateOutput.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateOutput.java
new file mode 100644
index 000000000..535565415
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/StateOutput.java
@@ -0,0 +1,217 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvent;
+import org.onap.policy.apex.model.eventmodel.concepts.AxEvents;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateOutput;
+import org.onap.policy.common.utils.validation.Assertions;
+
+/**
+ * This class is the output of a state, and is used by the engine to decide what the next state for execution is.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@Getter
+public class StateOutput {
+ // The state output has a state and an event
+ private final AxStateOutput stateOutputDefinition;
+ private AxEvent outputEventDef;
+ private final Map<AxArtifactKey, EnEvent> outputEvents;
+
+ /**
+ * Create a new state output from a state output definition.
+ *
+ * @param axStateOutput the state output definition
+ */
+ public StateOutput(final AxStateOutput axStateOutput) {
+ this(axStateOutput, new EnEvent(axStateOutput.getOutgoingEvent()));
+ }
+
+ /**
+ * Create a new state output with the given definition and event key.
+ *
+ * @param stateOutputDefinition the state output definition
+ * @param outputEvent the output event
+ */
+ public StateOutput(final AxStateOutput stateOutputDefinition, final EnEvent outputEvent) {
+ Assertions.argumentNotNull(stateOutputDefinition, "stateOutputDefinition may not be null");
+ Assertions.argumentNotNull(outputEvent, "outputEvent may not be null");
+
+ this.stateOutputDefinition = stateOutputDefinition;
+ this.outputEvents = new TreeMap<>();
+ if (stateOutputDefinition.getOutgoingEventSet() != null
+ && !stateOutputDefinition.getOutgoingEventSet().isEmpty()) {
+ stateOutputDefinition.getOutgoingEventSet()
+ .forEach(outEvent -> outputEvents.put(outEvent, new EnEvent(outEvent)));
+ } else {
+ outputEvents.put(outputEvent.getKey(), outputEvent);
+ }
+ outputEventDef = ModelService.getModel(AxEvents.class).get(stateOutputDefinition.getOutgoingEvent());
+ }
+
+ /**
+ * Gets the next state.
+ *
+ * @return the next state
+ */
+ public AxReferenceKey getNextState() {
+ return stateOutputDefinition.getNextState();
+ }
+
+ /**
+ * Transfer the fields from the incoming field map into the event.
+ *
+ * @param incomingEventDefinitionMap definitions of the incoming fields
+ * @param eventFieldMaps the event field map
+ * @throws StateMachineException on errors populating the event fields
+ */
+ public void setEventFields(final Map<String, AxEvent> incomingEventDefinitionMap,
+ final Map<String, Map<String, Object>> eventFieldMaps) throws StateMachineException {
+ Assertions.argumentNotNull(incomingEventDefinitionMap, "incomingFieldDefinitionMap may not be null");
+ Assertions.argumentNotNull(eventFieldMaps, "eventFieldMaps may not be null");
+
+ for (Entry<String, AxEvent> incomingEventDefinitionEntry : incomingEventDefinitionMap.entrySet()) {
+ String eventName = incomingEventDefinitionEntry.getKey();
+ AxEvent eventDef = incomingEventDefinitionEntry.getValue();
+ if (!eventDef.getParameterMap().keySet().equals(eventFieldMaps.get(eventName).keySet())) {
+ throw new StateMachineException(
+ "field definitions and values do not match for event " + eventDef.getId() + '\n'
+ + eventDef.getParameterMap().keySet() + '\n' + eventFieldMaps.get(eventName).keySet());
+ }
+ }
+ var updateOnceFlag = false;
+ if (!outputEvents.keySet().stream().map(AxArtifactKey::getName).collect(Collectors.toSet())
+ .equals(eventFieldMaps.keySet())) {
+ // when same task is used by multiple policies with different eventName but same fields,
+ // state outputs and task output events may be different
+ // in this case, update the output fields in the state output only once to avoid overwriting.
+ updateOnceFlag = true;
+ }
+ for (Entry<String, Map<String, Object>> eventFieldMapEntry : eventFieldMaps.entrySet()) {
+ String eventName = eventFieldMapEntry.getKey();
+ Map<String, Object> outputEventFields = eventFieldMapEntry.getValue();
+ AxEvent taskOutputEvent = incomingEventDefinitionMap.get(eventName);
+ EnEvent outputEventToUpdate = outputEvents.get(taskOutputEvent.getKey());
+
+ if (null == outputEventToUpdate) {
+ // happens only when same task is used by multiple policies with different eventName but same fields
+ // in this case, just match the fields and get the event in the stateOutput
+ Set<String> outputEventFieldNames = outputEventFields.keySet();
+ Optional<EnEvent> outputEventOpt = outputEvents.values().stream().filter(outputEvent -> outputEvent
+ .getAxEvent().getParameterMap().keySet().equals(outputEventFieldNames)).findFirst();
+ if (outputEventOpt.isEmpty()) {
+ throw new StateMachineException(
+ "Task output event field definition and state output event field doesn't match");
+ } else {
+ outputEventToUpdate = outputEventOpt.get();
+ }
+ }
+ updateOutputEventFields(taskOutputEvent, outputEventFields, outputEventToUpdate);
+ if (updateOnceFlag) {
+ break;
+ }
+ }
+ }
+
+ private void updateOutputEventFields(AxEvent taskOutputEvent, Map<String, Object> outputEventFields,
+ EnEvent outputEventToUpdate) throws StateMachineException {
+ for (Entry<String, Object> outputEventFieldEntry : outputEventFields.entrySet()) {
+ String fieldName = outputEventFieldEntry.getKey();
+ Object fieldValue = outputEventFieldEntry.getValue();
+ final AxField fieldDef = taskOutputEvent.getParameterMap().get(fieldName);
+
+ Set<AxArtifactKey> outgoingEventSet = new TreeSet<>();
+ if (null == stateOutputDefinition.getOutgoingEventSet()
+ || stateOutputDefinition.getOutgoingEventSet().isEmpty()) {
+ // if current state is not the final state, then the set could be empty.
+ // Just take the outgoingEvent field in this case
+ outgoingEventSet.add(stateOutputDefinition.getOutgoingEvent());
+ } else {
+ outgoingEventSet.addAll(stateOutputDefinition.getOutgoingEventSet());
+ }
+ // Check if this field is a field in the event
+ for (AxArtifactKey outputEventKey : outgoingEventSet) {
+ if (outputEventKey.equals(taskOutputEvent.getKey())) {
+ outputEventDef = ModelService.getModel(AxEvents.class).get(outputEventKey);
+ // Check if this field is a field in the state output event
+ if (!outputEventDef.getFields().contains(fieldDef)) {
+ throw new StateMachineException(
+ "field \"" + fieldName + "\" does not exist on event \"" + outputEventDef.getId() + "\"");
+ }
+ }
+ }
+ // Set the value in the correct output event
+ outputEventToUpdate.put(fieldName, fieldValue);
+ }
+ }
+
+ /**
+ * This method copies any fields that exist on the input event that also exist on the output event if they are not
+ * set on the output event.
+ *
+ * @param incomingEvent The incoming event to copy from
+ */
+ public void copyUnsetFields(final EnEvent incomingEvent) {
+ Assertions.argumentNotNull(incomingEvent, "incomingEvent may not be null");
+ Set<AxArtifactKey> outgoingEventSet = new TreeSet<>();
+ if (null == stateOutputDefinition.getOutgoingEventSet()
+ || stateOutputDefinition.getOutgoingEventSet().isEmpty()) {
+ // if current state is not the final state, then the set could be empty.
+ // Just take the outgoingEvent field in this case
+ outgoingEventSet.add(stateOutputDefinition.getOutgoingEvent());
+ } else {
+ outgoingEventSet.addAll(stateOutputDefinition.getOutgoingEventSet());
+ }
+ incomingEvent.forEach((inFieldName, inFieldValue) -> {
+ for (AxArtifactKey outputEventKey : outgoingEventSet) {
+ outputEventDef = ModelService.getModel(AxEvents.class).get(outputEventKey);
+ // Check if the field exists on the outgoing event
+ if (!outputEventDef.getParameterMap().containsKey(inFieldName)
+ // Check if the field is set in the outgoing event
+ || outputEvents.get(outputEventKey).containsKey(inFieldName)
+ // Now, check the fields have the same type
+ || !incomingEvent.getAxEvent().getParameterMap().get(inFieldName)
+ .equals(outputEvents.get(outputEventKey).getAxEvent().getParameterMap().get(inFieldName))) {
+ continue;
+ }
+ // All checks done, we can copy the value
+ outputEvents.get(outputEventKey).put(inFieldName, inFieldValue);
+ }
+ });
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskExecutor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskExecutor.java
new file mode 100644
index 000000000..ed5c0f271
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskExecutor.java
@@ -0,0 +1,334 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import static org.onap.policy.common.utils.validation.Assertions.argumentOfClassNotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import lombok.Getter;
+import lombok.NonNull;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.TaskParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.executor.context.TaskExecutionContext;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.onap.policy.apex.model.policymodel.concepts.AxTaskParameter;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This abstract class executes a task in a state of an Apex policy and is specialized by classes that implement
+ * execution of task logic.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public abstract class TaskExecutor
+ implements Executor<Map<String, Object>, Map<String, Map<String, Object>>, AxTask, ApexInternalContext> {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(TaskExecutor.class);
+
+ // Hold the task and context definitions for this task
+ @Getter
+ private Executor<?, ?, ?, ?> parent = null;
+ private AxTask axTask = null;
+ private ApexInternalContext internalContext = null;
+
+ // Holds the incoming and outgoing fields
+ private Map<String, Object> incomingFields = null;
+ private Map<String, Map<String, Object>> outgoingFieldsMap = null;
+
+ // The next task executor
+ private Executor<Map<String, Object>, Map<String, Map<String, Object>>, AxTask, ApexInternalContext> nextExecutor =
+ null;
+
+ // The task execution context; contains the facades for events and context to be used by tasks
+ // executed by this task
+ // executor
+ @Getter
+ private TaskExecutionContext executionContext = null;
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setContext(final Executor<?, ?, ?, ?> newParent, final AxTask newAxTask,
+ final ApexInternalContext newInternalContext) {
+ this.parent = newParent;
+ this.axTask = newAxTask;
+ this.internalContext = newInternalContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void prepare() throws StateMachineException {
+ LOGGER.debug("prepare:" + axTask.getKey().getId() + "," + axTask.getTaskLogic().getLogicFlavour() + ","
+ + axTask.getTaskLogic().getLogic());
+ argumentOfClassNotNull(axTask.getTaskLogic().getLogic(), StateMachineException.class,
+ "task logic cannot be null.");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Map<String, Map<String, Object>> execute(final long executionId, final Properties executionProperties,
+ final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException {
+ throw new StateMachineException(
+ "execute() not implemented on abstract TaskExecutor class, only on its subclasses");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePre(final long executionId, @NonNull final Properties executionProperties,
+ final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException {
+ LOGGER.debug("execute-pre:" + getSubject().getTaskLogic().getLogicFlavour() + ","
+ + getSubject().getKey().getId() + "," + getSubject().getTaskLogic().getLogic());
+
+ // Check that the incoming event has all the input fields for this state
+ Map<String, AxField> inputEventParameterMap = axTask.getInputEvent().getParameterMap();
+ final Set<String> missingTaskInputFields = new TreeSet<>(inputEventParameterMap.keySet());
+ missingTaskInputFields.removeAll(newIncomingFields.keySet());
+
+ // Remove fields from the set that are optional
+ missingTaskInputFields.removeIf(missingField -> inputEventParameterMap.get(missingField).getOptional());
+
+ if (!missingTaskInputFields.isEmpty()) {
+ throw new StateMachineException("task input fields \"" + missingTaskInputFields
+ + "\" are missing for task \"" + axTask.getKey().getId() + "\"");
+ }
+
+ // Record the incoming fields
+ this.incomingFields = newIncomingFields;
+
+ // Initiate the outgoing fields
+ outgoingFieldsMap = new TreeMap<>();
+ for (var outputEventEntry: axTask.getOutputEvents().entrySet()) {
+ Map<String, Object> outgoingFields = new TreeMap<>();
+ outputEventEntry.getValue().getParameterMap().keySet().forEach(field -> outgoingFields.put(field, null));
+ outgoingFieldsMap.put(outputEventEntry.getKey(), outgoingFields);
+ }
+ // Get task context object
+ executionContext = new TaskExecutionContext(this, executionId, executionProperties, getSubject(), getIncoming(),
+ outgoingFieldsMap.values(), getContext());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePost(final boolean returnValue) throws StateMachineException, ContextException {
+ if (!returnValue) {
+ String errorMessage = "execute-post: task logic execution failure on task \"" + axTask.getKey().getName()
+ + "\" in model " + internalContext.getKey().getId();
+ if (executionContext.getMessage() != null) {
+ errorMessage += ", user message: " + executionContext.getMessage();
+ }
+ LOGGER.warn(errorMessage);
+ throw new StateMachineException(errorMessage);
+ }
+
+ // Copy any unset fields from the input to the output if their data type and names are identical
+ axTask.getOutputEvents().entrySet().forEach(outputEventEntry -> outputEventEntry.getValue().getParameterMap()
+ .keySet().forEach(field -> copyInputField2Output(outputEventEntry.getKey(), field)));
+
+ // Finally, check that the outgoing fields have all the output fields defined for this state
+ // and, if not, output a list of missing fields
+ Map<String, Set<String>> missingTaskOutputFieldsMap = new TreeMap<>();
+ axTask.getOutputEvents().entrySet().forEach(outputEventEntry -> {
+ Set<String> missingTaskOutputFields = new TreeSet<>();
+ missingTaskOutputFields.addAll(outputEventEntry.getValue().getParameterMap().keySet());
+ String key = outputEventEntry.getKey();
+ missingTaskOutputFields.removeAll(outgoingFieldsMap.get(key).keySet());
+ missingTaskOutputFieldsMap.put(key, missingTaskOutputFields);
+ });
+
+ // Remove fields from the set that are optional
+ missingTaskOutputFieldsMap.entrySet()
+ .forEach(missingTaskOutputFieldsEntry -> missingTaskOutputFieldsEntry.getValue()
+ .removeIf(missingField -> axTask.getInputEvent().getParameterMap().containsKey(missingField)
+ || axTask.getOutputEvents().get(missingTaskOutputFieldsEntry.getKey()).getParameterMap()
+ .get(missingField).getOptional()));
+ missingTaskOutputFieldsMap.entrySet()
+ .removeIf(missingTaskOutputFieldsEntry -> missingTaskOutputFieldsEntry.getValue().isEmpty());
+ if (!missingTaskOutputFieldsMap.isEmpty()) {
+ throw new StateMachineException("Fields for task output events \"" + missingTaskOutputFieldsMap.keySet()
+ + "\" are missing for task \"" + axTask.getKey().getId() + "\"");
+
+ }
+
+ // Finally, check that the outgoing field map don't have any extra fields, if present, raise
+ // exception with the list of extra fields
+ final Map<String, Set<String>> extraTaskOutputFieldsMap = new TreeMap<>();
+ outgoingFieldsMap.entrySet().forEach(outgoingFieldsEntry -> extraTaskOutputFieldsMap
+ .put(outgoingFieldsEntry.getKey(), new TreeSet<>(outgoingFieldsEntry.getValue().keySet())));
+ extraTaskOutputFieldsMap.entrySet().forEach(extraTaskOutputFieldsEntry -> extraTaskOutputFieldsEntry.getValue()
+ .removeAll(axTask.getOutputEvents().get(extraTaskOutputFieldsEntry.getKey()).getParameterMap().keySet()));
+ extraTaskOutputFieldsMap.entrySet()
+ .removeIf(extraTaskOutputFieldsEntry -> extraTaskOutputFieldsEntry.getValue().isEmpty());
+ if (!extraTaskOutputFieldsMap.isEmpty()) {
+ throw new StateMachineException("task output event \"" + extraTaskOutputFieldsMap.keySet()
+ + "\" contains fields that are unwanted for task \"" + axTask.getKey().getId() + "\"");
+ }
+
+ String message =
+ "execute-post:" + axTask.getKey().getId() + ", returning fields " + outgoingFieldsMap.toString();
+ LOGGER.debug(message);
+ }
+
+ /**
+ * If the input field exists on the output and it is not set in the task, then it should be copied to the output.
+ *
+ * @param eventName the event name
+ * @param field the input field
+ */
+ private void copyInputField2Output(String eventName, String field) {
+ Map<String, Object> outgoingFields = outgoingFieldsMap.get(eventName);
+ // Check if the field exists and is not set on the output
+ if (outgoingFields.get(field) != null) {
+ return;
+ }
+
+ // This field is not in the output, check if it's on the input and is the same type
+ // (Note here, the output field definition has to exist so it's not null checked)
+ final AxField inputFieldDef = axTask.getInputEvent().getParameterMap().get(field);
+ final AxField outputFieldDef = axTask.getOutputEvents().get(eventName).getParameterMap().get(field);
+ if (inputFieldDef == null || !inputFieldDef.getSchema().equals(outputFieldDef.getSchema())) {
+ return;
+ }
+
+ // We have an input field that matches our output field, copy the value across
+ outgoingFields.put(field, getIncoming().get(field));
+ }
+
+ /**
+ * If taskParameters are provided in ApexConfig, then they will be updated in the Tasks.
+ * If taskId is empty, the task parameter is added/updated to all available tasks
+ * Otherwise, the task parameter is added/updated to the corresponding task only.
+ *
+ * @param taskParametersFromConfig the list of task parameters provided in ApexConfig during deployment
+ */
+ public void updateTaskParameters(List<TaskParameters> taskParametersFromConfig) {
+ Map<String, AxTaskParameter> taskParameters = getSubject().getTaskParameters();
+ if (null == taskParameters) {
+ taskParameters = new HashMap<>();
+ }
+ for (TaskParameters taskParameterFromConfig : taskParametersFromConfig) {
+ if (null == taskParameterFromConfig.getTaskId()
+ || getSubject().getId().equals(taskParameterFromConfig.getTaskId())) {
+ taskParameters.put(taskParameterFromConfig.getKey(),
+ new AxTaskParameter(new AxReferenceKey(), taskParameterFromConfig.getValue()));
+ }
+ }
+ getSubject().setTaskParameters(taskParameters);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void cleanUp() throws StateMachineException {
+ throw new StateMachineException("cleanUp() not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxArtifactKey getKey() {
+ return axTask.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxTask getSubject() {
+ return axTask;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ApexInternalContext getContext() {
+ return internalContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Map<String, Object> getIncoming() {
+ return incomingFields;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Map<String, Map<String, Object>> getOutgoing() {
+ return outgoingFieldsMap;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setNext(
+ final Executor<Map<String, Object>, Map<String, Map<String, Object>>, AxTask, ApexInternalContext> nextEx) {
+ this.nextExecutor = nextEx;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Executor<Map<String, Object>, Map<String, Map<String, Object>>, AxTask, ApexInternalContext> getNext() {
+ return nextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParameters(final ExecutorParameters parameters) {
+ // Not used
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutor.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutor.java
new file mode 100644
index 000000000..fa75db60e
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutor.java
@@ -0,0 +1,236 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor;
+
+import static org.onap.policy.common.utils.validation.Assertions.argumentNotNull;
+
+import java.util.Properties;
+import lombok.NonNull;
+import org.onap.policy.apex.context.ContextException;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.context.TaskSelectionExecutionContext;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This abstract class executes a the task selection logic of a state of an Apex policy and is specialized by classes
+ * that implement execution of task selection logic.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public abstract class TaskSelectExecutor implements Executor<EnEvent, AxArtifactKey, AxState, ApexInternalContext> {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(TaskSelectExecutor.class);
+
+ // Hold the state and context definitions for this task selector
+ private Executor<?, ?, ?, ?> parent = null;
+ private AxState axState = null;
+ private ApexInternalContext context = null;
+
+ // Holds the incoming event and outgoing task keys
+ private EnEvent incomingEvent = null;
+ private AxArtifactKey outgoingTaskKey = null;
+
+ // The next task selection executor
+ private Executor<EnEvent, AxArtifactKey, AxState, ApexInternalContext> nextExecutor = null;
+
+ // The task selection execution context; contains the facades for events and context to be used
+ // by tasks executed by
+ // this task selection executor
+ private TaskSelectionExecutionContext executionContext;
+
+ /**
+ * Gets the execution context.
+ *
+ * @return the execution context
+ */
+ protected TaskSelectionExecutionContext getExecutionContext() {
+ return executionContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setContext(final Executor<?, ?, ?, ?> newParent, final AxState newAxState,
+ final ApexInternalContext newContext) {
+ this.parent = newParent;
+ this.axState = newAxState;
+ this.context = newContext;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void prepare() throws StateMachineException {
+ LOGGER.debug("prepare:" + axState.getKey().getId() + "," + axState.getTaskSelectionLogic().getLogicFlavour()
+ + "," + axState.getTaskSelectionLogic().getLogic());
+ argumentNotNull(axState.getTaskSelectionLogic().getLogic(), "task selection logic cannot be null.");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxArtifactKey execute(final long executionId, final Properties executionProperties,
+ final EnEvent newIncomingEvent) throws StateMachineException, ContextException {
+ throw new StateMachineException("execute() not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePre(final long executionId, @NonNull final Properties executionProperties,
+ final EnEvent newIncomingEvent) throws StateMachineException {
+ LOGGER.debug("execute-pre:" + axState.getKey().getId() + "," + axState.getTaskSelectionLogic().getLogicFlavour()
+ + "," + axState.getTaskSelectionLogic().getLogic());
+
+ this.incomingEvent = newIncomingEvent;
+
+ // Initialize the returned task object so it can be set
+ outgoingTaskKey = new AxArtifactKey();
+
+ // Get task selection context object
+ executionContext = new TaskSelectionExecutionContext(this, executionId, getSubject(), getIncoming(),
+ getOutgoing(), getContext());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public final void executePost(final boolean returnValue) throws StateMachineException {
+ if (!returnValue) {
+ String errorMessage = "execute-post: task selection logic failed on state \"" + axState.getKey().getId()
+ + "\"";
+ if (executionContext.getMessage() != null) {
+ errorMessage += ", user message: " + executionContext.getMessage();
+ }
+ LOGGER.warn(errorMessage);
+ throw new StateMachineException(errorMessage);
+ }
+
+ if (outgoingTaskKey == null || AxArtifactKey.getNullKey().getName().equals(outgoingTaskKey.getName())) {
+ outgoingTaskKey = axState.getDefaultTask();
+ LOGGER.debug("execute-post:" + axState.getKey().getId() + ", returning default task");
+ return;
+ }
+
+ if (!axState.getTaskReferences().containsKey(outgoingTaskKey)) {
+ LOGGER.error("execute-post: task \"" + outgoingTaskKey.getId()
+ + "\" returned by task selection logic not defined on state \"" + axState.getKey().getId() + "\"");
+ throw new StateMachineException("task \"" + outgoingTaskKey.getId()
+ + "\" returned by task selection logic not defined on state \"" + axState.getKey().getId() + "\"");
+ }
+
+ LOGGER.debug("execute-post:" + axState.getKey().getId() + "," + ", returning task " + outgoingTaskKey.getId());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void cleanUp() throws StateMachineException {
+ throw new StateMachineException("cleanUp() not implemented on class");
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxReferenceKey getKey() {
+ return axState.getKey();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Executor<?, ?, ?, ?> getParent() {
+ return parent;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxState getSubject() {
+ return axState;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public ApexInternalContext getContext() {
+ return context;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setNext(final Executor<EnEvent, AxArtifactKey, AxState, ApexInternalContext> newNextExecutor) {
+ this.nextExecutor = newNextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Executor<EnEvent, AxArtifactKey, AxState, ApexInternalContext> getNext() {
+ return nextExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public EnEvent getIncoming() {
+ return incomingEvent;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public AxArtifactKey getOutgoing() {
+ return outgoingTaskKey;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParameters(final ExecutorParameters parameters) {
+ // Not used
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AbstractExecutionContext.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AbstractExecutionContext.java
new file mode 100644
index 000000000..3e6d13023
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AbstractExecutionContext.java
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import java.util.Properties;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.onap.policy.apex.context.SchemaHelper;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+/**
+ * Abstract class for the execution context for logic executions in logic being executed in an Apex engine. The
+ * logic must have easy access to its subject definition, the incoming and outgoing field contexts, as well as the
+ * policy, global, and external context.
+ */
+@Getter
+@RequiredArgsConstructor
+public class AbstractExecutionContext {
+ /** A constant <code>boolean true</code> value available for reuse e.g., for the return value */
+ public static final Boolean IS_TRUE = true;
+
+ /**
+ * A constant <code>boolean false</code> value available for reuse e.g., for the return value
+ */
+ public static final Boolean IS_FALSE = false;
+
+ // Standard coder for JSON converts
+ private static final StandardCoder STANDARD_CODER = new StandardCoder();
+
+ /** the execution ID for the current APEX policy execution instance. */
+ public final Long executionId;
+
+ // A message specified in the logic
+ @Setter
+ private String message;
+
+ // Execution properties for a policy execution
+ private final Properties executionProperties;
+
+ /**
+ * Get a JSON representation of an object.
+ *
+ * @param theObject the object to get a JSON representation of
+ * @return the JSON version of the object
+ * @throws CoderException on JSON coding errors
+ */
+ public String stringify2Json(final Object theObject) throws CoderException {
+ return stringify2Json(theObject, null);
+ }
+
+ /**
+ * Get a JSON representation of an object.
+ *
+ * @param theObject the object to get a JSON representation of
+ * @param schemaHelper a schema helper to use for the JSON conversion, if null, a standard conversion is done
+ * @return the JSON version of the object
+ * @throws CoderException on JSON coding errors
+ */
+ public String stringify2Json(final Object theObject, final SchemaHelper schemaHelper) throws CoderException {
+ if (schemaHelper == null) {
+ return STANDARD_CODER.encode(theObject);
+ } else {
+ return schemaHelper.marshal2String(theObject);
+ }
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacade.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacade.java
new file mode 100644
index 000000000..75d2aa82f
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacade.java
@@ -0,0 +1,103 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import lombok.AllArgsConstructor;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.service.ModelService;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxTasks;
+
+/**
+ * The Class AxStateFacade acts as a facade into the AxState class so that task logic can easily
+ * access information in an AxState instance.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ */
+@AllArgsConstructor
+public class AxStateFacade {
+ // CHECKSTYLE:OFF: checkstyle:visibilityModifier Logic has access to this field
+
+ /** The full definition information for the state. */
+ public final AxState state;
+
+ // CHECKSTYLE:ON: checkstyle:visibilityModifier
+
+ /**
+ * Gets the default task key of the state.
+ *
+ * @return the default task key
+ */
+ public AxArtifactKey getDefaultTaskKey() {
+ return state.getDefaultTask();
+ }
+
+ /**
+ * Gets the ID of the state.
+ *
+ * @return the ID
+ */
+ public String getId() {
+ return state.getKey().getId();
+ }
+
+ /**
+ * Gets the name of the state.
+ *
+ * @return the state name
+ */
+ public String getStateName() {
+ return state.getKey().getLocalName();
+ }
+
+ /**
+ * Check if a task is defined for a given task name on a state and, if so, return its key.
+ *
+ * @param taskName the name of the task to get
+ * @return the task key or null if it does not exist
+ */
+ public AxArtifactKey getTaskKey(final String taskName) {
+ if (taskName == null) {
+ return null;
+ }
+
+ return ModelService.getModel(AxTasks.class).get(taskName).getKey();
+ }
+
+ /**
+ * Check if a task is defined for a given task name on a state and, if so, return its key.
+ *
+ * @return unmodifiable list of names of tasks available
+ */
+ public List<String> getTaskNames() {
+ final Set<AxArtifactKey> tasks = state.getTaskReferences().keySet();
+ final List<String> ret = new ArrayList<>(tasks.size());
+ for (final AxArtifactKey task : tasks) {
+ ret.add(task.getName());
+ }
+ return Collections.unmodifiableList(ret);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacade.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacade.java
new file mode 100644
index 000000000..8a310c616
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacade.java
@@ -0,0 +1,141 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import lombok.AllArgsConstructor;
+import org.onap.policy.apex.context.SchemaHelper;
+import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
+import org.onap.policy.apex.core.engine.event.EnException;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineRuntimeException;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * The Class AxTaskFacade acts as a facade into the AxTask class so that task logic can easily
+ * access information in an AxTask instance.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ */
+@AllArgsConstructor
+public class AxTaskFacade {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(AxTaskFacade.class);
+
+ // CHECKSTYLE:OFF: checkstyle:visibilityModifier Logic has access to this field
+
+ /**
+ * The full definition of the task we are presenting a facade to, executing logic has full
+ * access to the task definition.
+ */
+ public final AxTask task;
+
+ // CHECKSTYLE:ON: checkstyle:visibilityModifier
+
+ /**
+ * Gets the name of the task.
+ *
+ * @return the task name
+ */
+ public String getTaskName() {
+ return task.getKey().getName();
+ }
+
+ /**
+ * Gets the task ID.
+ *
+ * @return the task ID
+ */
+ public String getId() {
+ return task.getId();
+ }
+
+ /**
+ * Creates a schema helper for an incoming field of this task.
+ *
+ * @param fieldName The name of the field to get a schema helper for
+ * @return the schema helper for this field
+ */
+ public SchemaHelper getInFieldSchemaHelper(final String fieldName) {
+ // Find the field for the field name
+ return getFieldSchemaHelper(fieldName, task.getInputEvent().getParameterMap().get(fieldName), "incoming");
+ }
+
+ /**
+ * Creates a schema helper for an outgoing field of this task.
+ * This method can be used only when there is a single event output as part of a task
+ *
+ * @param fieldName The name of the field to get a schema helper for
+ * @return the schema helper for this field
+ */
+ public SchemaHelper getOutFieldSchemaHelper(final String fieldName) {
+ // Find the field for the field name
+ return getFieldSchemaHelper(fieldName,
+ task.getOutputEvents().values().iterator().next().getParameterMap().get(fieldName), "outgoing");
+ }
+
+ /**
+ * Creates a schema helper for an outgoing field of this task.
+ * This method can be used when there are multiple event outputs from a task
+ *
+ * @param eventName the name of the event to which the field belongs to
+ * @param fieldName The name of the field to get a schema helper for
+ * @return the schema helper for this field
+ */
+ public SchemaHelper getOutFieldSchemaHelper(final String eventName, final String fieldName) {
+ // Find the field for the field name
+ return getFieldSchemaHelper(fieldName, task.getOutputEvents().get(eventName).getParameterMap().get(fieldName),
+ "outgoing");
+ }
+
+ /**
+ * Creates a schema helper for an incoming field of this task.
+ *
+ * @param fieldName The name of the field to get a schema helper for
+ * @param field the field
+ * @param directionString the direction string
+ * @return the schema helper for this field
+ */
+ private SchemaHelper getFieldSchemaHelper(final String fieldName, final AxField field,
+ final String directionString) {
+ // Find the field for the field name
+ if (field == null) {
+ final String message = "no " + directionString + " field with name \"" + fieldName + "\" defined on task \""
+ + task.getId() + "\"";
+ LOGGER.warn(message);
+ throw new StateMachineRuntimeException(message);
+ }
+
+ // Get a schema helper to handle translations of fields to and from the schema
+ try {
+ return new SchemaHelperFactory().createSchemaHelper(field.getKey(), field.getSchema());
+ } catch (final Exception e) {
+ final String message = "schema helper cannot be created for task field \"" + fieldName + "\" with key \""
+ + field.getId() + "\" with schema \"" + field.getSchema() + "\"";
+ LOGGER.warn(message, e);
+ throw new EnException(message, e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContext.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContext.java
new file mode 100644
index 000000000..12e350390
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContext.java
@@ -0,0 +1,164 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2020-2021 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.apex.context.ContextAlbum;
+import org.onap.policy.apex.context.ContextRuntimeException;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.executor.Executor;
+import org.onap.policy.apex.core.engine.executor.StateFinalizerExecutor;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * Container class for the execution context for state finalizer logic executions in a state being executed in an Apex
+ * engine. The state finalizer must have easy access to the state definition, the fields, as well as the policy, global,
+ * and external context.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ */
+@Getter
+public class StateFinalizerExecutionContext extends AbstractExecutionContext {
+ /**
+ * Logger for state finalizer execution, state finalizer logic can use this field to access and log to Apex logging.
+ */
+ private static final XLogger EXCEUTION_LOGGER =
+ XLoggerFactory.getXLogger("org.onap.policy.apex.executionlogging.StateFinalizerExecutionLogging");
+
+ // CHECKSTYLE:OFF: checkstyle:VisibilityModifier Logic has access to these field
+
+ /** A facade to the full state definition for the state finalizer logic being executed. */
+ public final AxStateFacade subject;
+
+ /**
+ * The list of state outputs for this state finalizer. The purpose of a state finalizer is to select a state output
+ * for a state from this list of state output names.
+ */
+ public final Set<String> stateOutputNames;
+
+ /**
+ * The fields of this state finalizer. A state finalizer receives this list of fields from a task and may use these
+ * fields to determine what state output to select. Once a state finalizer has selected a state output, it must
+ * marshal these fields so that they match the fields required for the event defined in the state output.
+ */
+ public final Map<String, Object> fields;
+
+ /**
+ * The state output that the state finalizer logic has selected for a state. The state finalizer logic sets this
+ * field in its logic after executing and the Apex engine uses this state output for this state.
+ */
+ @Getter
+ @Setter
+ private String selectedStateOutputName;
+
+ /**
+ * Logger for state finalizer execution, state finalizer logic can use this field to access and log to Apex logging.
+ */
+ public final XLogger logger = EXCEUTION_LOGGER;
+
+ // CHECKSTYLE:ON: checkstyle:visibilityModifier
+
+ // All available context albums
+ private final Map<String, ContextAlbum> context;
+
+ // Execution properties for a policy execution
+ @Getter
+ private Properties executionProperties;
+
+ /**
+ * Instantiates a new state finalizer execution context.
+ *
+ * @param stateFinalizerExecutor the state finalizer executor that requires context
+ * @param executionId the execution ID for the current APEX policy execution instance
+ * @param executionProperties the execution properties for task execution
+ * @param axState the state definition that is the subject of execution
+ * @param fields the fields to be manipulated by the state finalizer
+ * @param stateOutputNames the state output names, one of which will be selected by the state finalizer
+ * @param internalContext the execution context of the Apex engine in which the task is being executed
+ */
+ public StateFinalizerExecutionContext(final StateFinalizerExecutor stateFinalizerExecutor, final long executionId,
+ final Properties executionProperties, final AxState axState, final Map<String, Object> fields,
+ final Set<String> stateOutputNames, final ApexInternalContext internalContext) {
+ super(executionId, executionProperties);
+
+ subject = new AxStateFacade(axState);
+
+ this.fields = fields;
+ this.stateOutputNames = stateOutputNames;
+
+ // Set up the context albums for this task
+ context = new TreeMap<>();
+ for (final AxArtifactKey mapKey : subject.state.getContextAlbumReferences()) {
+ context.put(mapKey.getName(), internalContext.getContextAlbums().get(mapKey));
+ }
+
+ // Get the artifact stack of the users of the policy
+ final List<AxConcept> usedArtifactStack = new ArrayList<>();
+ for (Executor<?, ?, ?, ?> parent = stateFinalizerExecutor.getParent(); parent != null; parent =
+ parent.getParent()) {
+ // Add each parent to the top of the stack
+ usedArtifactStack.add(0, parent.getKey());
+ }
+
+ // Change the stack to an array
+ final AxConcept[] usedArtifactStackArray = usedArtifactStack.toArray(new AxConcept[usedArtifactStack.size()]);
+
+ // Set the user of the context
+ // Set the user of the context
+ for (final ContextAlbum contextAlbum : context.values()) {
+ contextAlbum.setUserArtifactStack(usedArtifactStackArray);
+ }
+ }
+
+ /**
+ * Return a context album if it exists in the context definition of this state.
+ *
+ * @param contextAlbumName The context album name
+ * @return The context album
+ * @throws ContextRuntimeException if the context album does not exist on the state for this executor
+ */
+ public ContextAlbum getContextAlbum(final String contextAlbumName) {
+ // Find the context album
+ final var foundContextAlbum = context.get(contextAlbumName);
+
+ // Check if the context album exists
+ if (foundContextAlbum != null) {
+ return foundContextAlbum;
+ } else {
+ throw new ContextRuntimeException("cannot find definition of context album \"" + contextAlbumName
+ + "\" on state \"" + subject.getId() + "\"");
+ }
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContext.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContext.java
new file mode 100644
index 000000000..a54252e05
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContext.java
@@ -0,0 +1,195 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextAlbum;
+import org.onap.policy.apex.context.ContextRuntimeException;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.executor.Executor;
+import org.onap.policy.apex.core.engine.executor.TaskExecutor;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.onap.policy.apex.model.policymodel.concepts.AxTaskParameter;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * Container class for the execution context for Task logic executions in a task being executed in an Apex engine. The
+ * task must have easy access to the task definition, the incoming and outgoing field contexts, as well as the policy,
+ * global, and external context.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ */
+@Getter
+public class TaskExecutionContext extends AbstractExecutionContext {
+ // Logger for task execution
+ private static final XLogger EXECUTION_LOGGER =
+ XLoggerFactory.getXLogger("org.onap.policy.apex.executionlogging.TaskExecutionLogging");
+
+ // CHECKSTYLE:OFF: checkstyle:VisibilityModifier Logic has access to these field
+
+ /** A facade to the full task definition for the task logic being executed. */
+ public final AxTaskFacade subject;
+
+ /**
+ * The incoming fields from the trigger event for the task. The task logic can access these fields when executing
+ * its logic.
+ */
+ public final Map<String, Object> inFields;
+
+ /**
+ * The outgoing fields from the task. The task logic can access and set these fields with its logic. A task outputs
+ * its result using these fields.
+ */
+ public final Map<String, Object> outFields;
+
+ /**
+ * The outgoing fields from the task. The task logic can access and set these fields with its logic. A task outputs
+ * its result using these fields.
+ */
+ public final Collection<Map<String, Object>> outFieldsList;
+
+ /**
+ * Logger for task execution, task logic can use this field to access and log to Apex logging.
+ */
+ public final XLogger logger = EXECUTION_LOGGER;
+
+ // CHECKSTYLE:ON: checkstyle:VisibilityModifier
+
+ // All available context albums
+ private final Map<String, ContextAlbum> context;
+
+ // The artifact stack of users of this context
+ private final List<AxConcept> usedArtifactStack;
+
+ // Parameters associated to a task
+ @Getter
+ private Map<String, String> parameters = new HashMap<>();
+
+ /**
+ * Instantiates a new task execution context.
+ *
+ * @param taskExecutor the task executor that requires context
+ * @param executionId the execution ID for the current APEX policy execution instance
+ * @param executionProperties the execution properties for task execution
+ * @param axTask the task definition that is the subject of execution
+ * @param inFields the in fields
+ * @param outFieldsList collection of the out fields
+ * @param internalContext the execution context of the Apex engine in which the task is being executed
+ */
+ public TaskExecutionContext(final TaskExecutor taskExecutor, final long executionId,
+ final Properties executionProperties, final AxTask axTask, final Map<String, Object> inFields,
+ final Collection<Map<String, Object>> outFieldsList, final ApexInternalContext internalContext) {
+ super(executionId, executionProperties);
+
+ // The subject is the task definition
+ subject = new AxTaskFacade(axTask);
+
+ // Populate parameters to be accessed in the task logic from the task parameters.
+ populateParameters(axTask.getTaskParameters());
+
+ // The input and output fields
+ this.inFields = Collections.unmodifiableMap(inFields);
+ this.outFieldsList = outFieldsList;
+ // if only a single output event needs to fired from a task, the outFields alone can be used too
+ if (outFieldsList.isEmpty()) {
+ this.outFields = new TreeMap<>();
+ } else {
+ this.outFields = outFieldsList.iterator().next();
+ }
+
+ // Set up the context albums for this task
+ context = new TreeMap<>();
+ for (final AxArtifactKey mapKey : subject.task.getContextAlbumReferences()) {
+ context.put(mapKey.getName(), internalContext.getContextAlbums().get(mapKey));
+ }
+
+ // Get the artifact stack of the users of the policy
+ usedArtifactStack = new ArrayList<>();
+ for (Executor<?, ?, ?, ?> parent = taskExecutor.getParent(); parent != null; parent = parent.getParent()) {
+ // Add each parent to the top of the stack
+ usedArtifactStack.add(0, parent.getKey());
+ }
+
+ // Change the stack to an array
+ final AxConcept[] usedArtifactStackArray = usedArtifactStack.toArray(new AxConcept[usedArtifactStack.size()]);
+
+ // Set the user of the context
+ for (final ContextAlbum contextAlbum : context.values()) {
+ contextAlbum.setUserArtifactStack(usedArtifactStackArray);
+ }
+ }
+
+ /**
+ * Populate parameters to be accessed in the task logic.
+ *
+ * @param taskParameters The task parameters
+ */
+ private void populateParameters(Map<String, AxTaskParameter> taskParameters) {
+ taskParameters.entrySet().forEach(taskParamEntry -> parameters.put(taskParamEntry.getKey(),
+ taskParamEntry.getValue().getTaskParameterValue()));
+ }
+
+ /**
+ * Return a context album if it exists in the context definition of this task.
+ *
+ * @param contextAlbumName The context album name
+ * @return The context album
+ * @throws ContextRuntimeException if the context album does not exist on the task for this executor
+ */
+ public ContextAlbum getContextAlbum(final String contextAlbumName) {
+ // Find the context album
+ final var foundContextAlbum = context.get(contextAlbumName);
+
+ // Check if the context album exists
+ if (foundContextAlbum != null) {
+ return foundContextAlbum;
+ } else {
+ throw new ContextRuntimeException("cannot find definition of context album \"" + contextAlbumName
+ + "\" on task \"" + subject.getId() + "\"");
+ }
+ }
+
+ /**
+ * Method to add fields to the output event list.
+ * @param fields the fields to be added
+ */
+ public void addFieldsToOutput(Map<String, Object> fields) {
+ for (Map<String, Object> outputFields : outFieldsList) {
+ if (outputFields.keySet().containsAll(fields.keySet())) {
+ outputFields.replaceAll((name, value) -> fields.get(name));
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContext.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContext.java
new file mode 100644
index 000000000..c79e907d9
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContext.java
@@ -0,0 +1,152 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.onap.policy.apex.context.ContextAlbum;
+import org.onap.policy.apex.context.ContextRuntimeException;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.event.EnEvent;
+import org.onap.policy.apex.core.engine.executor.Executor;
+import org.onap.policy.apex.core.engine.executor.TaskSelectExecutor;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * Container class for the execution context for Task Selection logic executions in a task being executed in an Apex
+ * engine. The task must have easy access to the state definition, the incoming and outgoing event contexts, as well as
+ * the policy, global, and external context.
+ *
+ * @author Sven van der Meer (sven.van.der.meer@ericsson.com)
+ */
+@Getter
+public class TaskSelectionExecutionContext extends AbstractExecutionContext {
+ // Logger for task execution
+ private static final XLogger EXECUTION_LOGGER =
+ XLoggerFactory.getXLogger("org.onap.policy.apex.executionlogging.TaskSelectionExecutionLogging");
+
+ // CHECKSTYLE:OFF: checkstyle:VisibilityModifier Logic has access to these field
+
+ /** A facade to the full state definition for the task selection logic being executed. */
+ public final AxStateFacade subject;
+
+ /**
+ * The incoming fields from the trigger event for the state. The task selection logic can access these fields to
+ * decide what task to select for the state.
+ */
+ public final Map<String, Object> inFields;
+
+ /**
+ * The task that the task selection logic has selected for a state. The task selection logic sets this field in its
+ * logic prior to executing and the Apex engine executes this task as the task for this state.
+ */
+ public final AxArtifactKey selectedTask;
+
+ /**
+ * Logger for task selection execution, task selection logic can use this field to access and log to Apex logging.
+ */
+ public final XLogger logger = EXECUTION_LOGGER;
+
+ // CHECKSTYLE:ON: checkstyle:VisibilityModifier
+
+ // All available context albums
+ private final Map<String, ContextAlbum> context;
+
+ /**
+ * Instantiates a new task selection execution context.
+ *
+ * @param taskSelectExecutor the task selection executor that requires context
+ * @param executionId the execution identifier
+ * @param axState the state definition that is the subject of execution
+ * @param incomingEvent the incoming event for the state
+ * @param outgoingKey the outgoing key for the task to execute in this state
+ * @param internalContext the execution context of the Apex engine in which the task is being executed
+ */
+ public TaskSelectionExecutionContext(final TaskSelectExecutor taskSelectExecutor, final long executionId,
+ final AxState axState, final EnEvent incomingEvent, final AxArtifactKey outgoingKey,
+ final ApexInternalContext internalContext) {
+ super(executionId, incomingEvent.getExecutionProperties());
+ // The subject is the state definition
+ subject = new AxStateFacade(axState);
+
+ // The events
+ inFields = incomingEvent;
+ selectedTask = outgoingKey;
+
+ // Set up the context albums for this task
+ // Set up the context albums for this task
+ context = new TreeMap<>();
+ for (final AxArtifactKey mapKey : subject.state.getContextAlbumReferences()) {
+ context.put(mapKey.getName(), internalContext.getContextAlbums().get(mapKey));
+ }
+
+ // Get the artifact stack of the users of the policy
+ final List<AxConcept> usedArtifactStack = new ArrayList<>();
+ for (Executor<?, ?, ?, ?> parent = taskSelectExecutor.getParent(); parent != null; parent =
+ parent.getParent()) {
+ // Add each parent to the top of the stack
+ usedArtifactStack.add(0, parent.getKey());
+ }
+
+ // Add the events to the artifact stack
+ usedArtifactStack.add(incomingEvent.getKey());
+
+ // Change the stack to an array
+ final AxConcept[] usedArtifactStackArray = usedArtifactStack.toArray(new AxConcept[usedArtifactStack.size()]);
+
+ // Set the user of the context
+ // Set the user of the context
+ for (final ContextAlbum contextAlbum : context.values()) {
+ contextAlbum.setUserArtifactStack(usedArtifactStackArray);
+ }
+ incomingEvent.setUserArtifactStack(usedArtifactStackArray);
+ }
+
+ /**
+ * Return a context album if it exists in the context definition of this state.
+ *
+ * @param contextAlbumName The context album name
+ * @return The context albumxxxxxx
+ * @throws ContextRuntimeException if the context album does not exist on the state for this executor
+ */
+ public ContextAlbum getContextAlbum(final String contextAlbumName) {
+ // Find the context album
+ final var foundContextAlbum = context.get(contextAlbumName);
+
+ // Check if the context album exists
+ if (foundContextAlbum != null) {
+ return foundContextAlbum;
+ } else {
+ throw new ContextRuntimeException("cannot find definition of context album \"" + contextAlbumName
+ + "\" on state \"" + subject.getId() + "\"");
+ }
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/package-info.java
new file mode 100644
index 000000000..71b5e455e
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/context/package-info.java
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides context and facades for executing tasks, task selection logic, and state finalizer
+ * logic. The public fields and methods of TaskExecutionContext,
+ * TaskSelectionExecutionContext and StateFinalizerExecutionContext are available to
+ * task logic, task selection logic, and state finalizer logic respectively when that logic is
+ * executing in an executor plugin under the control of an APEX engine.
+ *
+ * <p>The AxStateFacade and AxTaskFacade classes provide facades and convenience
+ * methods for state and task definition information for logic at execution time.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.executor.context;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineException.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineException.java
new file mode 100644
index 000000000..0b64eb481
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineException.java
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.exception;
+
+import org.onap.policy.apex.model.basicmodel.concepts.ApexException;
+
+/**
+ * This class will be called if an error occurs in an Apex state machine.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class StateMachineException extends ApexException {
+ private static final long serialVersionUID = -4245694568321686450L;
+
+ /**
+ * Instantiates a new state machine exception.
+ *
+ * @param message the message
+ */
+ public StateMachineException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Instantiates a new state machine exception.
+ *
+ * @param message the message
+ * @param ex the exception
+ */
+ public StateMachineException(final String message, final Exception ex) {
+ super(message, ex);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeException.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeException.java
new file mode 100644
index 000000000..447c88bf9
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeException.java
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.exception;
+
+import org.onap.policy.apex.model.basicmodel.concepts.ApexRuntimeException;
+
+/**
+ * This class will be called if a runtime error occurs in an Apex state machine.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class StateMachineRuntimeException extends ApexRuntimeException {
+ private static final long serialVersionUID = -4245694568321686450L;
+
+ /**
+ * Instantiates a new state machine exception.
+ *
+ * @param message the message
+ */
+ public StateMachineRuntimeException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Instantiates a new state machine exception.
+ *
+ * @param message the message
+ * @param ex the exception
+ */
+ public StateMachineRuntimeException(final String message, final Exception ex) {
+ super(message, ex);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/package-info.java
new file mode 100644
index 000000000..1cb433946
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/exception/package-info.java
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Contains exceptions that may be thrown during execution of an APEX engine.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.executor.exception;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/ExecutorFactoryImpl.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/ExecutorFactoryImpl.java
new file mode 100644
index 000000000..9dc841f48
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/ExecutorFactoryImpl.java
@@ -0,0 +1,223 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2019-2020 Nordix Foundation.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.executor.impl;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import org.onap.policy.apex.core.engine.EngineParameterConstants;
+import org.onap.policy.apex.core.engine.EngineParameters;
+import org.onap.policy.apex.core.engine.ExecutorParameters;
+import org.onap.policy.apex.core.engine.context.ApexInternalContext;
+import org.onap.policy.apex.core.engine.executor.Executor;
+import org.onap.policy.apex.core.engine.executor.ExecutorFactory;
+import org.onap.policy.apex.core.engine.executor.StateFinalizerExecutor;
+import org.onap.policy.apex.core.engine.executor.TaskExecutor;
+import org.onap.policy.apex.core.engine.executor.TaskSelectExecutor;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
+import org.onap.policy.apex.core.engine.executor.exception.StateMachineRuntimeException;
+import org.onap.policy.apex.model.policymodel.concepts.AxState;
+import org.onap.policy.apex.model.policymodel.concepts.AxStateFinalizerLogic;
+import org.onap.policy.apex.model.policymodel.concepts.AxTask;
+import org.onap.policy.common.parameters.ParameterService;
+import org.onap.policy.common.utils.validation.Assertions;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * The Class ExecutorFactoryImpl is a factory class that returns task selection logic and task logic executors depending
+ * on the type of logic executor has been specified for the task selection logic in a state or task logic in a task.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class ExecutorFactoryImpl implements ExecutorFactory {
+ // Get a reference to the logger
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(ExecutorFactoryImpl.class);
+
+ private final EngineParameters engineParameters;
+ // A map of logic flavours mapped to executor classes for plugins to executors for those logic flavours
+ private Map<String, Class<Executor<?, ?, ?, ?>>> taskExecutorPluginClassMap = new TreeMap<>();
+ private Map<String, Class<Executor<?, ?, ?, ?>>> taskSelectionExecutorPluginClassMap = new TreeMap<>();
+ private Map<String, Class<Executor<?, ?, ?, ?>>> stateFinalizerExecutorPluginClassMap = new TreeMap<>();
+
+ // A map of parameters for executors
+ private final Map<String, ExecutorParameters> implementationParameterMap = new TreeMap<>();
+
+ /**
+ * Constructor, builds the class map for executors.
+ *
+ * @throws StateMachineException on plugin creation errors
+ */
+ public ExecutorFactoryImpl() throws StateMachineException {
+ engineParameters = ParameterService.get(EngineParameterConstants.MAIN_GROUP_NAME);
+
+ Assertions.argumentOfClassNotNull(engineParameters, StateMachineException.class,
+ "Parameter \"engineParameters\" may not be null");
+
+ // Instantiate each executor class map entry
+ for (final Entry<String, ExecutorParameters> executorParameterEntry : engineParameters.getExecutorParameterMap()
+ .entrySet()) {
+ // Get classes for all types of executors for this logic type
+ taskExecutorPluginClassMap.put(executorParameterEntry.getKey(),
+ getExecutorPluginClass(executorParameterEntry.getValue().getTaskExecutorPluginClass()));
+ taskSelectionExecutorPluginClassMap.put(executorParameterEntry.getKey(),
+ getExecutorPluginClass(executorParameterEntry.getValue().getTaskSelectionExecutorPluginClass()));
+ stateFinalizerExecutorPluginClassMap.put(executorParameterEntry.getKey(),
+ getExecutorPluginClass(executorParameterEntry.getValue().getStateFinalizerExecutorPluginClass()));
+
+ // Save the executor implementation parameters
+ implementationParameterMap.put(executorParameterEntry.getKey(), executorParameterEntry.getValue());
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public TaskSelectExecutor getTaskSelectionExecutor(final Executor<?, ?, ?, ?> parentExecutor, final AxState state,
+ final ApexInternalContext context) {
+ if (!state.checkSetTaskSelectionLogic()) {
+ return null;
+ }
+
+ // Create task selection executor
+ final TaskSelectExecutor tsExecutor =
+ (TaskSelectExecutor) createExecutor(state.getTaskSelectionLogic().getLogicFlavour(),
+ taskSelectionExecutorPluginClassMap.get(state.getTaskSelectionLogic().getLogicFlavour()),
+ TaskSelectExecutor.class);
+ tsExecutor.setParameters(implementationParameterMap.get(state.getTaskSelectionLogic().getLogicFlavour()));
+ tsExecutor.setContext(parentExecutor, state, context);
+
+ return tsExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public TaskExecutor getTaskExecutor(final Executor<?, ?, ?, ?> parentExecutor, final AxTask task,
+ final ApexInternalContext context) {
+ // Create task executor
+ final var taskExecutor = (TaskExecutor) createExecutor(task.getTaskLogic().getLogicFlavour(),
+ taskExecutorPluginClassMap.get(task.getTaskLogic().getLogicFlavour()), TaskExecutor.class);
+ taskExecutor.setParameters(implementationParameterMap.get(task.getTaskLogic().getLogicFlavour()));
+ taskExecutor.setContext(parentExecutor, task, context);
+ taskExecutor.updateTaskParameters(engineParameters.getTaskParameters());
+ return taskExecutor;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public StateFinalizerExecutor getStateFinalizerExecutor(final Executor<?, ?, ?, ?> parentExecutor,
+ final AxStateFinalizerLogic logic, final ApexInternalContext context) {
+ // Create state finalizer executor
+ final StateFinalizerExecutor sfExecutor = (StateFinalizerExecutor) createExecutor(logic.getLogicFlavour(),
+ stateFinalizerExecutorPluginClassMap.get(logic.getLogicFlavour()), StateFinalizerExecutor.class);
+ sfExecutor.setParameters(implementationParameterMap.get(logic.getLogicFlavour()));
+ sfExecutor.setContext(parentExecutor, logic, context);
+
+ return sfExecutor;
+ }
+
+ /**
+ * Get an executor class for a given executor plugin class name.
+ *
+ * @param executorClassName The name of the executor plugin class
+ * @return an executor class
+ * @throws StateMachineException on plugin instantiation errors
+ */
+ @SuppressWarnings("unchecked")
+ private Class<Executor<?, ?, ?, ?>> getExecutorPluginClass(final String executorClassName)
+ throws StateMachineException {
+ // It's OK for an executor class not to be defined as long as it's not called
+ if (executorClassName == null) {
+ return null;
+ }
+
+ // Get the class for the executor using reflection
+ Class<? extends Object> executorPluginClass = null;
+ try {
+ executorPluginClass = Class.forName(executorClassName);
+ } catch (final ClassNotFoundException e) {
+ LOGGER.error("Apex executor class not found for executor plugin \"" + executorClassName + "\"", e);
+ throw new StateMachineException(
+ "Apex executor class not found for executor plugin \"" + executorClassName + "\"", e);
+ }
+
+ // Check the class is an executor
+ if (!Executor.class.isAssignableFrom(executorPluginClass)) {
+ LOGGER.error("Specified Apex executor plugin class \"{}\" does not implment the Executor interface",
+ executorClassName);
+ throw new StateMachineException("Specified Apex executor plugin class \"" + executorClassName
+ + "\" does not implment the Executor interface");
+ }
+
+ return (Class<Executor<?, ?, ?, ?>>) executorPluginClass;
+ }
+
+ /**
+ * Get an instance of an executor plugin class of the specified type and super type.
+ *
+ * @param logicFlavour The logic flavour of the logic
+ * @param executorClass The sub-class of the executor type to be instantiated
+ * @param executorSuperClass The super type of the class of executor to be instantiated
+ * @return The instantiated class
+ */
+ private Executor<?, ?, ?, ?> createExecutor(final String logicFlavour,
+ final Class<Executor<?, ?, ?, ?>> executorClass,
+ final Class<? extends Executor<?, ?, ?, ?>> executorSuperClass) {
+ // It's OK for an executor class not to be defined but it's not all right to try and create
+ // a non-defined
+ // executor class
+ if (executorClass == null) {
+ final String errorMessage = "Executor plugin class not defined for \"" + logicFlavour
+ + "\" executor of type \"" + executorSuperClass.getName() + "\"";
+ LOGGER.error(errorMessage);
+ throw new StateMachineRuntimeException(errorMessage);
+ }
+
+ // Create an executor for the specified logic flavour
+ Object executorObject = null;
+ try {
+ executorObject = executorClass.getDeclaredConstructor().newInstance();
+ } catch (final Exception e) {
+ final String errorMessage = "Instantiation error on \"" + logicFlavour + "\" executor of type \""
+ + executorClass.getName() + "\"";
+ LOGGER.error(errorMessage, e);
+ throw new StateMachineRuntimeException(errorMessage, e);
+ }
+
+ // Check the class is the correct type of executor
+ if (!(executorSuperClass.isAssignableFrom(executorObject.getClass()))) {
+ final String errorMessage = "Executor on \"" + logicFlavour + "\" of type \"" + executorClass
+ + "\" is not an instance of \"" + executorSuperClass.getName() + "\"";
+
+ LOGGER.error(errorMessage);
+ throw new StateMachineRuntimeException(errorMessage);
+ }
+
+ return (Executor<?, ?, ?, ?>) executorObject;
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/package-info.java
new file mode 100644
index 000000000..66e23e667
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/impl/package-info.java
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Contains factories for creating executors for tasks, state fianlizers, and task selectors.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.executor.impl;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/executor/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/executor/package-info.java
new file mode 100644
index 000000000..062e1ae49
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/executor/package-info.java
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Implements state, task, task selection, and state finalizer execution for the APEX engine.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.executor;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/EventMonitor.java b/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/EventMonitor.java
new file mode 100644
index 000000000..ca564ca81
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/EventMonitor.java
@@ -0,0 +1,118 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.engine.monitoring;
+
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
+import org.onap.policy.apex.model.basicmodel.concepts.AxConcept;
+import org.onap.policy.apex.model.basicmodel.concepts.AxReferenceKey;
+import org.onap.policy.apex.model.eventmodel.concepts.AxField;
+import org.slf4j.ext.XLogger;
+import org.slf4j.ext.XLoggerFactory;
+
+/**
+ * This class is used to monitor event parameter gets and sets.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+public class EventMonitor {
+ // Logger for this class
+ private static final XLogger LOGGER = XLoggerFactory.getXLogger(EventMonitor.class);
+
+ /**
+ * Monitor get on an event parameter.
+ *
+ * @param eventParameter The event parameter to monitor
+ * @param value the value of the event parameter
+ * @param userArtifactStack the keys of the artifacts using the event at the moment
+ */
+ public void monitorGet(final AxField eventParameter, final Object value, final AxConcept[] userArtifactStack) {
+ var monitorGetString = monitor("GET", userArtifactStack, eventParameter, value);
+ LOGGER.trace(monitorGetString);
+ }
+
+ /**
+ * Monitor set on an event parameter.
+ *
+ * @param eventParameter The event parameter to monitor
+ * @param value the value of the event parameter
+ * @param userArtifactStack the keys of the artifacts using the event at the moment
+ */
+ public void monitorSet(final AxField eventParameter, final Object value, final AxConcept[] userArtifactStack) {
+ var monitorSetString = monitor("SET", userArtifactStack, eventParameter, value);
+ LOGGER.trace(monitorSetString);
+ }
+
+ /**
+ * Monitor remove on an event parameter.
+ *
+ * @param eventParameter The event parameter to monitor
+ * @param removedValue the value of the event parameter
+ * @param userArtifactStack the keys of the artifacts using the event at the moment
+ */
+ public void monitorRemove(final AxField eventParameter, final Object removedValue,
+ final AxConcept[] userArtifactStack) {
+ var monitorRemoveString = monitor("REMOVE", userArtifactStack, eventParameter, removedValue);
+ LOGGER.trace(monitorRemoveString);
+ }
+
+ /**
+ * Monitor the user artifact stack.
+ *
+ * @param preamble the preamble
+ * @param userArtifactStack The user stack to print
+ * @param eventParameter The event parameter that we are monitoring
+ * @param value The value of the target object
+ * @return the string
+ */
+ private String monitor(final String preamble, final AxConcept[] userArtifactStack, final AxField eventParameter,
+ final Object value) {
+ final var builder = new StringBuilder();
+
+ builder.append(preamble);
+ builder.append(",[");
+
+ if (userArtifactStack != null) {
+ var first = true;
+ for (final AxConcept stackKey : userArtifactStack) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(',');
+ }
+ if (stackKey instanceof AxArtifactKey) {
+ builder.append(((AxArtifactKey) stackKey).getId());
+ } else if (stackKey instanceof AxReferenceKey) {
+ builder.append(((AxReferenceKey) stackKey).getId());
+ } else {
+ builder.append(stackKey.toString());
+ }
+ }
+ }
+ builder.append("],");
+
+ builder.append(eventParameter.toString());
+ builder.append("=");
+ builder.append(value);
+
+ return builder.toString();
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/package-info.java
new file mode 100644
index 000000000..38834687d
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/monitoring/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides monitoring of APEX policy execution. It monitors events as they trigger Apex policies,
+ * pass between the various states of a policy, and are emitted by a policy.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine.monitoring;
diff --git a/core/src/main/java/org/onap/policy/apex/core/engine/package-info.java b/core/src/main/java/org/onap/policy/apex/core/engine/package-info.java
new file mode 100644
index 000000000..a836cd949
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/engine/package-info.java
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides the core engine implementation for Apex. It builds a state machine for execution for each policy in its
+ * policy model. It provides the infrastructure for running policies and their states, for running executors provided by
+ * executor plugins, for supplying events and context to running policies, states, and tasks, and for handling event
+ * transmission into and out of policies and between states in policies.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.engine;
diff --git a/core/src/main/java/org/onap/policy/apex/core/infrastructure/messaging/MessagingException.java b/core/src/main/java/org/onap/policy/apex/core/infrastructure/messaging/MessagingException.java
new file mode 100644
index 000000000..dfaf4629f
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/infrastructure/messaging/MessagingException.java
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.infrastructure.messaging;
+
+/**
+ * This class will be called if an error occurs in Java handling.
+ *
+ * @author Liam Fallon
+ */
+public class MessagingException extends Exception {
+ private static final long serialVersionUID = -6375859029774312663L;
+
+ /**
+ * Instantiates a new messaging exception.
+ *
+ * @param message the message
+ */
+ public MessagingException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Instantiates a new messaging exception.
+ *
+ * @param message the message
+ * @param exception the e
+ */
+ public MessagingException(final String message, final Exception exception) {
+ super(message, exception);
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ApplicationThreadFactory.java b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ApplicationThreadFactory.java
new file mode 100644
index 000000000..9345abaaa
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ApplicationThreadFactory.java
@@ -0,0 +1,118 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2021 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.infrastructure.threading;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.Getter;
+
+/**
+ * This class provides a thread factory for use by classes that require thread factories to handle concurrent operation.
+ *
+ * @author Sajeevan Achuthan (sajeevan.achuthan@ericsson.com)
+ */
+public class ApplicationThreadFactory implements ThreadFactory {
+ private static final String HYPHEN = "-";
+ private static final String APPLICATION_NAME = "Apex-";
+ private static final AtomicInteger NEXT_POOL_NUMBER = new AtomicInteger();
+ private final ThreadGroup group;
+ private final AtomicInteger nextThreadNumber = new AtomicInteger();
+
+ @Getter
+ private final String name;
+ @Getter
+ private final long stackSize;
+ @Getter
+ private final int threadPriority;
+
+ /**
+ * Instantiates a new application thread factory with a default stack size and normal thread priority.
+ *
+ * @param nameLocal the name local
+ */
+ public ApplicationThreadFactory(final String nameLocal) {
+ this(nameLocal, 0);
+ }
+
+ /**
+ * Instantiates a new application thread factory with a default normal thread priority.
+ *
+ * @param nameLocal the name local
+ * @param stackSize the stack size
+ */
+ public ApplicationThreadFactory(final String nameLocal, final long stackSize) {
+ this(nameLocal, stackSize, Thread.NORM_PRIORITY);
+ }
+
+ /**
+ * Instantiates a new application thread factory with a specified thread priority.
+ *
+ * @param nameLocal the name local
+ * @param stackSize the stack size
+ * @param threadPriority the thread priority
+ */
+ public ApplicationThreadFactory(final String nameLocal, final long stackSize, final int threadPriority) {
+ final var s = System.getSecurityManager();
+ group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ name = APPLICATION_NAME + nameLocal + HYPHEN + NEXT_POOL_NUMBER.getAndIncrement();
+ this.stackSize = stackSize;
+ this.threadPriority = threadPriority;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public Thread newThread(final Runnable runnable) {
+ final Thread thisThread;
+ if (stackSize > 0) {
+ thisThread = new Thread(group, runnable, name + ':' + nextThreadNumber.getAndIncrement(), stackSize);
+ } else {
+ thisThread = new Thread(group, runnable, name + ':' + nextThreadNumber.getAndIncrement());
+ }
+ if (thisThread.isDaemon()) {
+ thisThread.setDaemon(false);
+ }
+ thisThread.setPriority(threadPriority);
+
+ return thisThread;
+ }
+
+ /**
+ * Stop group threads.
+ */
+ public void stopGroupThreads() {
+ group.interrupt();
+ group.list();
+
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "ApplicationThreadFactory [nextPoolNumber=" + NEXT_POOL_NUMBER + ",nextThreadNumber=" + nextThreadNumber
+ + ", name=" + name + ", stackSize=" + stackSize + ", threadPriority=" + threadPriority + "]";
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ThreadUtilities.java b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ThreadUtilities.java
new file mode 100644
index 000000000..58939d622
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/ThreadUtilities.java
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. All rights reserved.
+ * Modifications Copyright (C) 2021 AT&T Intellectual Property. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.apex.core.infrastructure.threading;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * This class is a helper class for carrying out common threading tasks.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ThreadUtilities {
+
+ /**
+ * Sleeps for the specified number of milliseconds, hiding interrupt handling.
+ *
+ * @param milliseconds the milliseconds
+ * @return true, if successful
+ */
+ public static boolean sleep(final long milliseconds) {
+ try {
+ Thread.sleep(milliseconds);
+ } catch (final InterruptedException e) {
+ // restore the interrupt status
+ Thread.currentThread().interrupt();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/package-info.java b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/package-info.java
new file mode 100644
index 000000000..dc0b9ee40
--- /dev/null
+++ b/core/src/main/java/org/onap/policy/apex/core/infrastructure/threading/package-info.java
@@ -0,0 +1,27 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2016-2018 Ericsson. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+/**
+ * Provides factories and utility functions for threads.
+ *
+ * @author Liam Fallon (liam.fallon@ericsson.com)
+ */
+
+package org.onap.policy.apex.core.infrastructure.threading;