diff options
Diffstat (limited to 'core/src')
76 files changed, 9495 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; diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/EngineParametersTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/EngineParametersTest.java new file mode 100644 index 000000000..5427c3515 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/EngineParametersTest.java @@ -0,0 +1,94 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications 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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.onap.policy.apex.context.parameters.ContextParameters; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the executor parameters. + * + */ +public class EngineParametersTest { + + @Test + public void test() { + EngineParameters pars = new EngineParameters(); + pars.setName("Name"); + assertEquals("Name", pars.getName()); + + ContextParameters contextPars = new ContextParameters(); + + pars.setContextParameters(contextPars); + assertEquals(contextPars, pars.getContextParameters()); + + Map<String, ExecutorParameters> executorParameterMap = new LinkedHashMap<>(); + executorParameterMap.put("Executor", new ExecutorParameters()); + pars.setExecutorParameterMap(executorParameterMap); + assertEquals(executorParameterMap, pars.getExecutorParameterMap()); + + List<TaskParameters> taskParameters = new ArrayList<>(); + taskParameters.add(new TaskParameters("param1key", "param1value", "param1taskId")); + taskParameters.add(new TaskParameters("param1key", "param1value", null)); + pars.setTaskParameters(taskParameters); + + assertThat(pars.validate().getResult()).isNull(); + assertTrue(pars.validate().isValid()); + + ParameterService.register(pars); + ParameterService.deregister(pars); + } + + @Test + public void test_invalid() { + EngineParameters pars = new EngineParameters(); + pars.setName("Name"); + assertEquals("Name", pars.getName()); + + ContextParameters contextPars = new ContextParameters(); + + pars.setContextParameters(contextPars); + assertEquals(contextPars, pars.getContextParameters()); + + Map<String, ExecutorParameters> executorParameterMap = Map.of("Executor", new ExecutorParameters()); + pars.setExecutorParameterMap(executorParameterMap); + assertEquals(executorParameterMap, pars.getExecutorParameterMap()); + + pars.setTaskParameters(List.of(new TaskParameters(null, "param1value", "param1taskId"))); + assertFalse(pars.validate().isValid()); + pars.setTaskParameters(List.of(new TaskParameters(" ", "param1value", "param1taskId"))); + assertFalse(pars.validate().isValid()); + pars.setTaskParameters(List.of(new TaskParameters("param1key", "", "param1taskId"))); + assertFalse(pars.validate().isValid()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/ExecutorParametersTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/ExecutorParametersTest.java new file mode 100644 index 000000000..784580422 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/ExecutorParametersTest.java @@ -0,0 +1,60 @@ +/*- + * ============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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the executor parameters. + * + */ +public class ExecutorParametersTest { + + @Test + public void test() { + ExecutorParameters pars = new ExecutorParameters(); + pars.setName("Name"); + assertEquals("Name", pars.getName()); + pars.setStateFinalizerExecutorPluginClass("some.state.finalizer.plugin.class"); + assertEquals("some.state.finalizer.plugin.class", pars.getStateFinalizerExecutorPluginClass()); + pars.setTaskExecutorPluginClass("some.task.executor.plugin.class"); + assertEquals("some.task.executor.plugin.class", pars.getTaskExecutorPluginClass()); + pars.setTaskSelectionExecutorPluginClass("some.task.selection.executor.plugin.class"); + assertEquals("some.task.selection.executor.plugin.class", pars.getTaskSelectionExecutorPluginClass()); + + assertEquals("ExecutorParameters [name=Name, taskExecutorPluginClass=some.task.executor.plugin.class, " + + "taskSelectionExecutorPluginClass=some.task.selection.executor.plugin.class, " + + "stateFinalizerExecutorPluginClass=some.state.finalizer.plugin.class]", pars.toString()); + + assertThat(pars.validate().getResult()).isNull(); + assertTrue(pars.validate().isValid()); + + + ParameterService.register(pars); + ParameterService.deregister(pars); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/context/ApexInternalContextTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/context/ApexInternalContextTest.java new file mode 100644 index 000000000..19828f6e2 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/context/ApexInternalContextTest.java @@ -0,0 +1,174 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2019-2020 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.context; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.apex.context.ContextException; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.DistributorParameters; +import org.onap.policy.apex.context.parameters.LockManagerParameters; +import org.onap.policy.apex.context.parameters.PersistorParameters; +import org.onap.policy.apex.context.parameters.SchemaParameters; +import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextAlbum; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the Apex engine internal context class. + */ +public class ApexInternalContextTest { + + private AxPolicyModel policyModel; + private AxPolicyModel newVersionPolicyModel; + private AxPolicyModel newPolicyModel; + private AxContextAlbum album; + private AxContextAlbum newAlbum; + private AxPolicyModel incompatiblePolicyModel; + + /** + * Initialize parameters. + */ + @Before + public void registerParameters() { + ParameterService.register(new SchemaParameters()); + ParameterService.register(new DistributorParameters()); + ParameterService.register(new LockManagerParameters()); + ParameterService.register(new PersistorParameters()); + } + + /** + * Create policy model. + */ + @Before + public void createPolicyModels() { + AxArtifactKey modelKey = new AxArtifactKey("PolicyModel:0.0.1"); + policyModel = new AxPolicyModel(modelKey); + + AxArtifactKey schemaKey = new AxArtifactKey("Schema:0.0.1"); + AxContextSchema schema = new AxContextSchema(schemaKey, "Java", "java.lang.String"); + policyModel.getSchemas().getSchemasMap().put(schemaKey, schema); + + AxArtifactKey albumKey = new AxArtifactKey("Album:0.0.1"); + album = new AxContextAlbum(albumKey, "Policy", true, schemaKey); + + policyModel.getAlbums().getAlbumsMap().put(albumKey, album); + + AxArtifactKey newVersionModelKey = new AxArtifactKey("PolicyModel:0.0.2"); + newVersionPolicyModel = new AxPolicyModel(newVersionModelKey); + + newVersionPolicyModel.getSchemas().getSchemasMap().put(schemaKey, schema); + AxContextAlbum compatibleAlbum = new AxContextAlbum(albumKey, "Global", true, schemaKey); + newVersionPolicyModel.getAlbums().getAlbumsMap().put(albumKey, compatibleAlbum); + + AxArtifactKey anotherAlbumKey = new AxArtifactKey("AnotherAlbum:0.0.1"); + AxContextAlbum anotherAlbum = new AxContextAlbum(anotherAlbumKey, "Policy", true, schemaKey); + + newVersionPolicyModel.getAlbums().getAlbumsMap().put(anotherAlbumKey, anotherAlbum); + + AxArtifactKey incompatibleModelKey = new AxArtifactKey("IncompatiblePolicyModel:0.0.2"); + incompatiblePolicyModel = new AxPolicyModel(incompatibleModelKey); + + AxArtifactKey incompatibleSchemaKey = new AxArtifactKey("IncompatibleSchema:0.0.1"); + AxContextSchema incompatibleSchema = new AxContextSchema(incompatibleSchemaKey, "Java", "java.lang.Integer"); + incompatiblePolicyModel.getSchemas().getSchemasMap().put(incompatibleSchemaKey, incompatibleSchema); + + AxContextAlbum incompatibleAlbum = new AxContextAlbum(albumKey, "Policy", true, incompatibleSchemaKey); + incompatiblePolicyModel.getAlbums().getAlbumsMap().put(albumKey, incompatibleAlbum); + + AxArtifactKey newModelKey = new AxArtifactKey("NewPolicyModel:0.0.1"); + newPolicyModel = new AxPolicyModel(newModelKey); + + AxArtifactKey newSchemaKey = new AxArtifactKey("NewSchema:0.0.1"); + AxContextSchema newSchema = new AxContextSchema(newSchemaKey, "Java", "java.lang.Integer"); + newPolicyModel.getSchemas().getSchemasMap().put(newSchemaKey, newSchema); + + AxArtifactKey newAlbumKey = new AxArtifactKey("NewAlbum:0.0.1"); + newAlbum = new AxContextAlbum(newAlbumKey, "Policy", true, newSchemaKey); + + newPolicyModel.getAlbums().getAlbumsMap().put(newAlbumKey, newAlbum); + } + + /** + * Deregister parameters. + */ + @After + public void deregisterParameters() { + ParameterService.deregister(ContextParameterConstants.DISTRIBUTOR_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.LOCKING_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.PERSISTENCE_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.SCHEMA_GROUP_NAME); + } + + @Test + public void testAlbumInit() throws ContextException { + assertThatThrownBy(() -> new ApexInternalContext(null)) + .hasMessage("internal context update failed, supplied model is null"); + ApexInternalContext context = new ApexInternalContext(policyModel); + + assertEquals(policyModel.getKey(), context.getKey()); + assertEquals(1, context.getContextAlbums().size()); + + AxArtifactKey albumKey = new AxArtifactKey("Album:0.0.1"); + assertEquals(album.getId(), context.get(albumKey).getKey().getId()); + assertEquals(album.getId(), context.get(albumKey.getName()).getKey().getId()); + assertEquals(album.getId(), context.get(albumKey.getName(), albumKey.getVersion()).getKey().getId()); + assertEquals(album.getId(), context.getAll(albumKey.getName()).iterator().next().getKey().getId()); + assertEquals(album.getId(), + context.getAll(albumKey.getName(), albumKey.getVersion()).iterator().next().getKey().getId()); + + context.clear(); + assertEquals(1, context.getContextAlbums().size()); + + assertEquals("ApexInternalContext [contextAlbums={AxArtifactKey:(name=Album,version=0.0.1)", + context.toString().substring(0, 76)); + } + + @Test + public void testAlbumUpdate() throws ContextException { + ApexInternalContext context = new ApexInternalContext(policyModel); + assertThatThrownBy(() -> context.update(null, false)) + .hasMessage("internal context update failed, supplied model is null"); + + assertEquals(policyModel.getKey().getId(), context.getKey().getId()); + assertEquals(1, context.getContextAlbums().size()); + + assertThatThrownBy(() -> context.update(incompatiblePolicyModel, false)).hasMessage( + "internal context update failed on context album \"Album:0.0.1\" " + "in model \"PolicyModel:0.0.1\", " + + "schema \"Schema:0.0.1\" on existing context model does not equal " + + "schema \"IncompatibleSchema:0.0.1\" on incoming model"); + + assertEquals(policyModel.getKey().getId(), context.getKey().getId()); + + context.update(newVersionPolicyModel, false); + assertEquals(newVersionPolicyModel.getKey().getId(), context.getKey().getId()); + + context.update(newPolicyModel, true); + assertEquals(newPolicyModel.getKey().getId(), context.getKey().getId()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImplTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImplTest.java new file mode 100644 index 000000000..3b682acd6 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/ApexEngineImplTest.java @@ -0,0 +1,521 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2019-2021 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.engine.impl; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import io.prometheus.client.CollectorRegistry; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.DistributorParameters; +import org.onap.policy.apex.context.parameters.LockManagerParameters; +import org.onap.policy.apex.context.parameters.PersistorParameters; +import org.onap.policy.apex.context.parameters.SchemaParameters; +import org.onap.policy.apex.core.engine.EngineParameterConstants; +import org.onap.policy.apex.core.engine.EngineParameters; +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.StateMachineExecutor; +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.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.contextmodel.concepts.AxContextAlbum; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.enginemodel.concepts.AxEngineState; +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.AxPolicy; +import org.onap.policy.apex.model.policymodel.concepts.AxPolicyModel; +import org.onap.policy.apex.model.policymodel.concepts.AxState; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the engine implementation. + */ +public class ApexEngineImplTest { + private static final String ENGINE_ID = "Engine:0.0.1"; + + private AxPolicyModel policyModel; + private AxPolicyModel incompatiblePolicyModel; + private AxPolicyModel policyModelWithStates; + + @Mock + StateMachineHandler smHandlerMock; + + /** + * Set up services. + */ + @BeforeClass + public static void setup() { + ParameterService.register(new SchemaParameters()); + ParameterService.register(new DistributorParameters()); + ParameterService.register(new LockManagerParameters()); + ParameterService.register(new PersistorParameters()); + ParameterService.register(new EngineParameters()); + } + + /** + * Set up mocking. + */ + @Before + public void initializeMocking() throws ApexException { + MockitoAnnotations.initMocks(this); + + Mockito.doThrow(new StateMachineException("mocked state machine exception", + new IOException("nexted exception"))).when(smHandlerMock).execute(Mockito.anyObject()); + } + + /** + * Create policy models. + */ + @Before + public void createPolicyModels() { + AxArtifactKey modelKey = new AxArtifactKey("PolicyModel:0.0.1"); + policyModel = new AxPolicyModel(modelKey); + + AxArtifactKey schemaKey = new AxArtifactKey("Schema:0.0.1"); + AxContextSchema schema = new AxContextSchema(schemaKey, "Java", "java.lang.String"); + policyModel.getSchemas().getSchemasMap().put(schemaKey, schema); + + AxArtifactKey albumKey = new AxArtifactKey("Album:0.0.1"); + AxContextAlbum album = new AxContextAlbum(albumKey, "Policy", true, schemaKey); + + policyModel.getAlbums().getAlbumsMap().put(albumKey, album); + + AxEvents events = new AxEvents(); + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + AxEvent event = new AxEvent(eventKey, "event.name.space", "source", "target"); + events.getEventMap().put(eventKey, event); + policyModel.setEvents(events); + + AxArtifactKey incompatibleModelKey = new AxArtifactKey("IncompatiblePolicyModel:0.0.2"); + incompatiblePolicyModel = new AxPolicyModel(incompatibleModelKey); + + AxArtifactKey incompatibleSchemaKey = new AxArtifactKey("IncompatibleSchema:0.0.1"); + AxContextSchema incompatibleSchema = new AxContextSchema(incompatibleSchemaKey, "Java", "java.lang.Integer"); + incompatiblePolicyModel.getSchemas().getSchemasMap().put(incompatibleSchemaKey, incompatibleSchema); + + AxContextAlbum incompatibleAlbum = new AxContextAlbum(albumKey, "Policy", true, incompatibleSchemaKey); + incompatiblePolicyModel.getAlbums().getAlbumsMap().put(albumKey, incompatibleAlbum); + + AxArtifactKey modelKeyStates = new AxArtifactKey("PolicyModelStates:0.0.1"); + policyModelWithStates = new AxPolicyModel(modelKeyStates); + policyModelWithStates.getSchemas().getSchemasMap().put(schemaKey, schema); + policyModelWithStates.getAlbums().getAlbumsMap().put(albumKey, album); + policyModelWithStates.setEvents(events); + + AxPolicy policy0 = new AxPolicy(new AxArtifactKey("Policy0:0.0.1")); + AxState state0 = new AxState(new AxReferenceKey(policy0.getKey(), "state0")); + state0.setTrigger(eventKey); + policy0.getStateMap().put(state0.getKey().getLocalName(), state0); + policy0.setFirstState(state0.getKey().getLocalName()); + + policyModelWithStates.getPolicies().getPolicyMap().put(policy0.getKey(), policy0); + + AxPolicy policy1 = new AxPolicy(new AxArtifactKey("Policy1:0.0.1")); + AxState state1 = new AxState(new AxReferenceKey(policy1.getKey(), "state1")); + state1.setTrigger(eventKey); + policy1.getStateMap().put(state1.getKey().getLocalName(), state1); + policy1.setFirstState(state1.getKey().getLocalName()); + + policyModelWithStates.getPolicies().getPolicyMap().put(policy1.getKey(), policy1); + } + + /** + * Clear registrations. + */ + @AfterClass + public static void teardown() { + ParameterService.deregister(ContextParameterConstants.SCHEMA_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.DISTRIBUTOR_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.LOCKING_GROUP_NAME); + ParameterService.deregister(ContextParameterConstants.PERSISTENCE_GROUP_NAME); + ParameterService.deregister(EngineParameterConstants.MAIN_GROUP_NAME); + } + + @Test + public void testSanity() throws ApexException { + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + assertNotNull(engine); + assertEquals(engineKey, engine.getKey()); + + assertThatThrownBy(engine::start).hasMessage("start()<-Engine:0.0.1,STOPPED, cannot start engine, " + + "engine has not been initialized, its model is not loaded"); + + assertThatThrownBy(engine::stop) + .hasMessage("stop()<-Engine:0.0.1,STOPPED, cannot stop engine, " + "engine is already stopped"); + + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + assertEquals(0, engine.getEngineContext().size()); + assertEquals(engineKey, engine.getEngineStatus().getKey()); + assertNull(engine.getInternalContext()); + + engine.clear(); + + assertThatThrownBy(() -> engine.addEventListener(null, null)) + .hasMessage("addEventListener()<-Engine:0.0.1,STOPPED, listenerName is null"); + + assertThatThrownBy(() -> engine.addEventListener("myListener", null)) + .hasMessage("addEventListener()<-Engine:0.0.1,STOPPED, listener is null"); + + assertThatThrownBy(() -> engine.removeEventListener(null)) + .hasMessage("removeEventListener()<-Engine:0.0.1,STOPPED, listenerName is null"); + } + + @Test + public void testListener() throws ApexException { + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + + engine.addEventListener("myListener", new DummyListener()); + engine.removeEventListener("myListener"); + + assertNull(engine.createEvent(null)); + + assertFalse(engine.handleEvent(null)); + + assertThatThrownBy(() -> engine.updateModel(null, false)) + .hasMessage("updateModel()<-Engine:0.0.1, Apex model is not defined, it has a null value"); + + engine.updateModel(policyModel, false); + + // Force a context exception + ModelService.registerModel(AxPolicyModel.class, new AxPolicyModel()); + assertThatThrownBy(() -> engine.updateModel(incompatiblePolicyModel, false)) + .hasMessage("updateModel()<-Engine:0.0.1, error setting the context for engine \"Engine:0.0.1\""); + + engine.updateModel(policyModel, false); + + assertNotNull(engine.getInternalContext()); + assertEquals(1, engine.getEngineContext().size()); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertThatThrownBy(engine::start) + .hasMessage("start()<-Engine:0.0.1,READY, cannot start engine, engine not in state STOPPED"); + + assertThatThrownBy(engine::clear) + .hasMessage("clear()<-Engine:0.0.1,READY, cannot clear engine, engine is not stopped"); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.clear(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + assertThatThrownBy(engine::start).hasMessage("start()<-Engine:0.0.1,STOPPED, cannot start engine, " + + "engine has not been initialized, its model is not loaded"); + + engine.updateModel(policyModel, false); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertNull(engine.createEvent(null)); + } + + @Test + public void testEventKey() throws ApexException { + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + engine.updateModel(policyModel, false); + engine.start(); + + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + EnEvent event = engine.createEvent(eventKey); + assertEquals(eventKey, event.getKey()); + + assertTrue(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.addEventListener("myListener", new DummyListener()); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertThatThrownBy(() -> engine.updateModel(policyModel, false)).hasMessage( + "updateModel()<-Engine:0.0.1, cannot update model, engine should be stopped but is in state READY"); + + assertTrue(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.addEventListener("badListener", new DummyEnEventListener()); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertFalse(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.removeEventListener("badListener"); + engine.addEventListener("slowListener", new DummySlowEnEventListener()); + } + + @Test + public void testState() throws InterruptedException, ApexException { + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + assertNotNull(engine); + assertEquals(engineKey, engine.getKey()); + + engine.updateModel(policyModel, false); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + DummySlowEnEventListener slowListener = new DummySlowEnEventListener(); + engine.addEventListener("slowListener", slowListener); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + EnEvent event = engine.createEvent(eventKey); + assertEquals(eventKey, event.getKey()); + + // 1 second is less than the 3 second wait on engine stopping + slowListener.setWaitTime(1000); + (new Thread() { + @Override + public void run() { + engine.handleEvent(event); + } + }).start(); + await().atLeast(50, TimeUnit.MILLISECONDS).until(() -> engine.getState().equals(AxEngineState.EXECUTING)); + assertEquals(AxEngineState.EXECUTING, engine.getState()); + checkAxEngineStateMetric(AxEngineState.EXECUTING); + + assertFalse(engine.handleEvent(event)); + assertNotNull(engine.createEvent(eventKey)); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + // 4 seconds is more than the 3 second wait on engine stopping + slowListener.setWaitTime(4000); + (new Thread() { + @Override + public void run() { + engine.handleEvent(event); + } + }).start(); + + await().atLeast(50, TimeUnit.MILLISECONDS).until(() -> engine.getState().equals(AxEngineState.EXECUTING)); + assertEquals(AxEngineState.EXECUTING, engine.getState()); + checkAxEngineStateMetric(AxEngineState.EXECUTING); + assertThatThrownBy(engine::stop) + .hasMessage("stop()<-Engine:0.0.1,STOPPED, error stopping engine, engine stop timed out"); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + engine.clear(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + } + + @Test + public void testStateMachineError() throws InterruptedException, IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, SecurityException, ApexException { + + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + assertNotNull(engine); + assertEquals(engineKey, engine.getKey()); + + engine.updateModel(policyModel, false); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + final Field smHandlerField = engine.getClass().getDeclaredField("stateMachineHandler"); + smHandlerField.setAccessible(true); + smHandlerField.set(engine, smHandlerMock); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + EnEvent event = engine.createEvent(eventKey); + assertEquals(eventKey, event.getKey()); + + assertFalse(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + Mockito.doThrow(new StateMachineException("mocked state machine exception", + new IOException("nexted exception"))).when(smHandlerMock).start(); + assertThatThrownBy(engine::start).hasMessage("updateModel()<-Engine:0.0.1, error starting the engine state " + + "machines \"Engine:0.0.1\""); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.clear(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + } + + @Test + public void testStateMachineHandler() throws InterruptedException, IllegalArgumentException, IllegalAccessException, + NoSuchFieldException, SecurityException, ApexException { + AxArtifactKey engineKey = new AxArtifactKey(ENGINE_ID); + ApexEngineImpl engine = (ApexEngineImpl) new ApexEngineFactory().createApexEngine(engineKey); + assertNotNull(engine); + assertEquals(engineKey, engine.getKey()); + + engine.updateModel(policyModelWithStates, false); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + EnEvent event = engine.createEvent(eventKey); + assertEquals(eventKey, event.getKey()); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + // Can't work, state is not fully defined + assertFalse(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + final Field smHandlerField = engine.getClass().getDeclaredField("stateMachineHandler"); + smHandlerField.setAccessible(true); + StateMachineHandler smHandler = (StateMachineHandler) smHandlerField.get(engine); + + final Field smExecutorMapField = smHandler.getClass().getDeclaredField("stateMachineExecutorMap"); + smExecutorMapField.setAccessible(true); + @SuppressWarnings("unchecked") + HashMap<AxEvent, StateMachineExecutor> smExMap = (HashMap<AxEvent, StateMachineExecutor>) smExecutorMapField + .get(smHandler); + + assertEquals(1, smExMap.size()); + DummySmExecutor dummyExecutor = new DummySmExecutor(null, event.getKey()); + smExMap.put(event.getAxEvent(), dummyExecutor); + ApexInternalContext internalContext = new ApexInternalContext(policyModelWithStates); + assertThatThrownBy(() -> dummyExecutor.setContext(null, null, internalContext)) + .isInstanceOf(NullPointerException.class); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + assertThatThrownBy(engine::start).hasMessageContaining("updateModel()<-Engine:0.0.1, error starting the " + + "engine state machines \"Engine:0.0.1\""); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.start(); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + // Works, Dummy executor fakes event execution + assertTrue(engine.handleEvent(event)); + assertEquals(AxEngineState.READY, engine.getState()); + checkAxEngineStateMetric(AxEngineState.READY); + + engine.stop(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + + engine.clear(); + assertEquals(AxEngineState.STOPPED, engine.getState()); + checkAxEngineStateMetric(AxEngineState.STOPPED); + } + + private void checkAxEngineStateMetric(AxEngineState state) { + Double stateMetric = CollectorRegistry.defaultRegistry + .getSampleValue("pdpa_engine_state", new String[]{"engine_instance_id"}, new String[]{ENGINE_ID}); + assertEquals(stateMetric.intValue(), state.getStateIdentifier()); + } +}
\ No newline at end of file diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyEnEventListener.java b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyEnEventListener.java new file mode 100644 index 000000000..b429295ad --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyEnEventListener.java @@ -0,0 +1,40 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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.EnEventListener; +import org.onap.policy.apex.core.engine.event.EnEvent; +import org.onap.policy.apex.model.basicmodel.concepts.ApexException; + +/** + * Dummy engine event listener for unit test. + * + */ +public class DummyEnEventListener implements EnEventListener { + + /** + * {@inheritDoc}. + */ + @Override + public void onEnEvent(EnEvent enEvent) throws ApexException { + throw new ApexException("not implemented on dummy class"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyListener.java b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyListener.java new file mode 100644 index 000000000..6256ff498 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummyListener.java @@ -0,0 +1,40 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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.EnEventListener; +import org.onap.policy.apex.core.engine.event.EnEvent; +import org.onap.policy.apex.model.basicmodel.concepts.ApexException; + +/** + * Dummy engine event listener for unit test. + * + */ +public class DummyListener implements EnEventListener { + + /** + * {@inheritDoc}. + */ + @Override + public void onEnEvent(EnEvent enEvent) throws ApexException { + // Do nothing + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySlowEnEventListener.java b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySlowEnEventListener.java new file mode 100644 index 000000000..e794a7d04 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySlowEnEventListener.java @@ -0,0 +1,56 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2019-2021 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.impl; + +import org.onap.policy.apex.core.engine.engine.EnEventListener; +import org.onap.policy.apex.core.engine.event.EnEvent; +import org.onap.policy.apex.model.basicmodel.concepts.ApexException; + +/** + * Dummy engine event listener for unit test. + * Thread.sleep is used to simulate a slow event listener. + * + */ +public class DummySlowEnEventListener implements EnEventListener { + + private long waitTime; + + /** + * {@inheritDoc}. + */ + @Override + public void onEnEvent(EnEvent enEvent) throws ApexException { + try { + Thread.sleep(waitTime); + } catch (InterruptedException ie) { + //Do nothing + } + } + + public long getWaitTime() { + return waitTime; + } + + public void setWaitTime(long waitTime) { + this.waitTime = waitTime; + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySmExecutor.java b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySmExecutor.java new file mode 100644 index 000000000..df4d9279e --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/engine/impl/DummySmExecutor.java @@ -0,0 +1,85 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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.engine.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Properties; +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.model.basicmodel.concepts.AxArtifactKey; + +/** + * Dummy state machine executor for testing. + */ +public class DummySmExecutor extends StateMachineExecutor { + private boolean cleanupWorks = false; + private boolean prepareWorks; + + /** + * Constructor. + * + * @param executorFactory the factory for executors + * @param owner the owner key + */ + public DummySmExecutor(ExecutorFactory executorFactory, AxArtifactKey owner) { + super(executorFactory, owner); + } + + /** + * {@inheritDoc}. + */ + @Override + public void prepare() throws StateMachineException { + if (prepareWorks) { + prepareWorks = false; + } else { + prepareWorks = true; + throw new StateMachineException("dummy state machine executor exception"); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public Collection<EnEvent> execute(final long executionId, final Properties executionProperties, + final EnEvent incomingEvent) { + return List.of(incomingEvent); + } + + /** + * {@inheritDoc}. + */ + @Override + public void cleanUp() throws StateMachineException { + if (cleanupWorks) { + cleanupWorks = false; + } else { + cleanupWorks = true; + throw new StateMachineException("dummy state machine executor exception"); + } + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/event/DummyAxKey.java b/core/src/test/java/org/onap/policy/apex/core/engine/event/DummyAxKey.java new file mode 100644 index 000000000..eafa7f419 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/event/DummyAxKey.java @@ -0,0 +1,131 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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 java.util.List; +import org.apache.commons.lang3.NotImplementedException; +import org.onap.policy.apex.model.basicmodel.concepts.AxConcept; +import org.onap.policy.apex.model.basicmodel.concepts.AxKey; +import org.onap.policy.apex.model.basicmodel.concepts.AxValidationResult; + +/** + * Dummy Key Class. + */ +public class DummyAxKey extends AxKey { + private static final long serialVersionUID = 964899169013353800L; + + /** + * {@inheritDoc}. + */ + @Override + public int compareTo(AxConcept concept) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public String getId() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Compatibility getCompatibility(AxKey otherKey) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public boolean isCompatible(AxKey otherKey) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxKey getKey() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public List<AxKey> getKeys() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxValidationResult validate(AxValidationResult result) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void clean() { + throw new NotImplementedException("Not implemented on dummy class"); + + } + + /** + * {@inheritDoc}. + */ + @Override + public boolean equals(Object otherObject) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public String toString() { + return "Dummy Key"; + } + + /** + * {@inheritDoc}. + */ + @Override + public int hashCode() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxConcept copyTo(AxConcept target) { + throw new NotImplementedException("Not implemented on dummy class"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/event/EnEventTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnEventTest.java new file mode 100644 index 000000000..49b4f80d2 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnEventTest.java @@ -0,0 +1,170 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020-2021 Nordix Foundation + * Modifications Copyright (C) 2022 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.event; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.SchemaParameters; +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.basicmodel.service.ModelService; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchemas; +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.common.parameters.ParameterService; + +/** + * Test the engine event class. + */ +public class EnEventTest { + /** + * Set up the services. + */ + @Before + public void setupServices() { + ModelService.registerModel(AxContextSchemas.class, new AxContextSchemas()); + ModelService.registerModel(AxEvents.class, new AxEvents()); + ParameterService.register(new SchemaParameters()); + } + + /** + * Tear down the services. + */ + @After + public void teardownServices() { + ModelService.deregisterModel(AxContextSchema.class); + ModelService.deregisterModel(AxEvents.class); + ParameterService.deregister(ContextParameterConstants.SCHEMA_GROUP_NAME); + } + + @Test + public void testEnEvent() { + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + assertThatThrownBy(() -> new EnEvent(eventKey)) + .hasMessage("event definition is null or was not found in model service"); + assertThatThrownBy(() -> new EnEvent((AxEvent) null)) + .hasMessage("event definition is null or was not found in model service"); + AxEvent axEvent = new AxEvent(eventKey, "a.name.space", "some source", "some target"); + ModelService.getModel(AxEvents.class).getEventMap().put(eventKey, axEvent); + + EnEvent event = new EnEvent(eventKey); + assertEquals(eventKey, event.getKey()); + assertEquals("Event:0.0.1", event.getId()); + assertEquals("Event", event.getName()); + assertEquals(axEvent, event.getAxEvent()); + event.setExecutionId(123454321L); + assertEquals(123454321L, event.getExecutionId()); + event.setExceptionMessage("Something happened"); + assertEquals("Something happened", event.getExceptionMessage()); + AxConcept[] usedArtifactStackArray = + { eventKey }; + event.setUserArtifactStack(usedArtifactStackArray); + assertEquals(usedArtifactStackArray.length, event.getUserArtifactStack().length); + assertEquals("EnEvent [axEvent=AxEvent:(key=AxArtifactKey:(name=Event,version=0.0.1),nameSpace=a.name.space," + + "source=some source,target=some target,parameter={},toscaPolicyState=), " + + "userArtifactStack=[AxArtifactKey:(name=Event,version=0.0.1)], map={}]", event.toString()); + assertThatThrownBy(() -> event.put(null, null)) + .hasMessage("null keys are illegal on method parameter \"key\""); + assertThatThrownBy(() -> event.put("NonField", null)) + .hasMessage("parameter with key \"NonField\" not defined on event \"Event\""); + } + + @Test + public void testAxEvent() { + AxArtifactKey eventKey = new AxArtifactKey("Event:0.0.1"); + AxEvent axEvent = new AxEvent(eventKey, "a.name.space", "some source", "some target"); + ModelService.getModel(AxEvents.class).getEventMap().put(eventKey, axEvent); + EnEvent event = new EnEvent(eventKey); + + AxReferenceKey fieldKey = new AxReferenceKey("Parent", "0.0.1", "MyParent", "MyField"); + AxArtifactKey fieldSchemaKey = new AxArtifactKey("FieldSchema:0.0.1"); + AxField axField = new AxField(fieldKey, fieldSchemaKey); + + AxConcept[] usedArtifactStackArrayMultiple = + { eventKey, fieldKey, new DummyAxKey() }; + event.setUserArtifactStack(usedArtifactStackArrayMultiple); + + AxContextSchema schema = new AxContextSchema(fieldSchemaKey, "Java", "java.lang.Integer"); + ModelService.getModel(AxContextSchemas.class).getSchemasMap().put(fieldSchemaKey, schema); + + Map<String, AxField> parameterMap = new LinkedHashMap<>(); + parameterMap.put("MyField", axField); + ModelService.getModel(AxEvents.class).get(eventKey).setParameterMap(parameterMap); + + event.put("MyField", null); + assertNull(event.get("MyField")); + + assertThatThrownBy(() -> event.put("MyField", "Hello")) + .hasMessage("Parent:0.0.1:MyParent:MyField: object \"Hello\" of class \"java.lang.String\" " + + "not compatible with class \"java.lang.Integer\""); + event.put("MyField", 123); + assertEquals(123, event.get("MyField")); + + assertTrue(event.keySet().contains("MyField")); + assertTrue(event.values().contains(123)); + assertEquals("MyField", event.entrySet().iterator().next().getKey()); + + event.putAll(event); + + assertThatThrownBy(() -> event.get(null)) + .hasMessage("null values are illegal on method parameter \"key\""); + assertThatThrownBy(() -> event.get("NonField")) + .hasMessage("parameter with key NonField not defined on this event"); + assertThatThrownBy(() -> event.remove(null)) + .hasMessage("null keys are illegal on method parameter \"key\""); + assertThatThrownBy(() -> event.remove("NonField")) + .hasMessage("parameter with key NonField not defined on this event"); + event.remove("MyField"); + assertNull(event.get("MyField")); + + event.put("MyField", 123); + assertEquals(123, event.get("MyField")); + event.clear(); + assertNull(event.get("MyField")); + + assertNotEquals(0, event.hashCode()); + // disabling sonar because this code tests the equals() method + assertEquals(event, event); // NOSONAR + assertNotNull(event); + Map<String, Object> hashMap = new HashMap<>(); + assertNotEquals(event, hashMap); + + EnEvent otherEvent = new EnEvent(eventKey); + assertEquals(event, otherEvent); + } +}
\ No newline at end of file diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/event/EnExceptionTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnExceptionTest.java new file mode 100644 index 000000000..5ca49e479 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnExceptionTest.java @@ -0,0 +1,41 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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 static org.junit.Assert.assertEquals; + +import java.io.IOException; +import org.junit.Test; + +/** + * Test the event exception class. + */ +public class EnExceptionTest { + + @Test + public void testEnException() { + EnException ene = new EnException("Message"); + assertEquals("Message", ene.getMessage()); + + ene = new EnException("Message", new IOException()); + assertEquals("Message", ene.getMessage()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/event/EnFieldTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnFieldTest.java new file mode 100644 index 000000000..dbe2fa3c8 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/event/EnFieldTest.java @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.event; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.SchemaParameters; +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.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchemas; +import org.onap.policy.apex.model.eventmodel.concepts.AxField; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the EnField class. + */ +public class EnFieldTest { + /** + * Set up the services. + */ + @Before + public void setupServices() { + AxContextSchemas schemas = new AxContextSchemas(); + ModelService.registerModel(AxContextSchemas.class, schemas); + ParameterService.register(new SchemaParameters()); + } + + /** + * Tear down the services. + */ + @After + public void teardownServices() { + ModelService.deregisterModel(AxContextSchemas.class); + ParameterService.deregister(ContextParameterConstants.SCHEMA_GROUP_NAME); + } + + @Test + public void testEnField() { + AxReferenceKey fieldKey = new AxReferenceKey("Parent", "0.0.1", "MyParent", "MyField"); + AxArtifactKey fieldSchemaKey = new AxArtifactKey("FieldSchema:0.0.1"); + AxField axField = new AxField(fieldKey, fieldSchemaKey); + + assertThatThrownBy(() -> new EnField(axField, null)) + .hasMessage("schema helper cannot be created for parameter with key \"Parent:0.0.1:MyParent:My" + + "Field\" with schema \"AxArtifactKey:(name=FieldSchema,version=0.0.1)\""); + AxContextSchema schema = new AxContextSchema(fieldSchemaKey, "Java", "java.lang.Integer"); + ModelService.getModel(AxContextSchemas.class).getSchemasMap().put(fieldSchemaKey, schema); + EnField field = new EnField(axField, 123); + + assertEquals(axField, field.getAxField()); + assertEquals(123, field.getValue()); + assertEquals(fieldKey, field.getKey()); + assertEquals("MyField", field.getName()); + assertEquals("org.onap.policy.apex.context.impl.schema.java.JavaSchemaHelper", + field.getSchemaHelper().getClass().getName()); + assertEquals(123, field.getAssignableValue()); + assertEquals("EnField [axField=AxField:(key=AxReferenceKey:(parentKeyName=Parent,parentKeyVersion=0.0.1," + + "parentLocalName=MyParent,localName=MyField),fieldSchemaKey=AxArtifactKey:" + + "(name=FieldSchema,version=0.0.1),optional=false), value=123]", field.toString()); + assertTrue(field.isAssignableValue()); + + field = new EnField(axField, "Hello"); + assertFalse(field.isAssignableValue()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyFailingTaskExecutor.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyFailingTaskExecutor.java new file mode 100644 index 000000000..4d4fb639f --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyFailingTaskExecutor.java @@ -0,0 +1,30 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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; + +/** + * Dummy task executor for testing. + */ +public class DummyFailingTaskExecutor extends TaskExecutor { + public DummyFailingTaskExecutor() throws InstantiationException { + throw new InstantiationException("I always fail"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyStateFinalizerExecutor.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyStateFinalizerExecutor.java new file mode 100644 index 000000000..e4e3ddf0d --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyStateFinalizerExecutor.java @@ -0,0 +1,65 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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 java.util.Map; +import java.util.Properties; +import lombok.NoArgsConstructor; +import org.onap.policy.apex.context.ContextException; +import org.onap.policy.apex.core.engine.executor.exception.StateMachineException; + +/** + * Dummy state finalizer executor for testing. + */ +@NoArgsConstructor +public class DummyStateFinalizerExecutor extends StateFinalizerExecutor { + private boolean override; + + private boolean returnBad; + + public DummyStateFinalizerExecutor(final boolean override) { + this.override = override; + } + + /** + * {@inheritDoc}. + */ + @Override + public String execute(final long executionId, final Properties executionProperties, + final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException { + + if (!override) { + super.execute(executionId, executionProperties, newIncomingFields); + } + + if (returnBad) { + return "stateOutputBad"; + } else { + return "stateOutput1"; + } + } + + public void setReturnBad(boolean returnBad) { + this.returnBad = returnBad; + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskExecutor.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskExecutor.java new file mode 100644 index 000000000..8172eefcc --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskExecutor.java @@ -0,0 +1,91 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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.Map; +import java.util.Properties; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.onap.policy.apex.context.ContextException; +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.eventmodel.concepts.AxEvent; +import org.onap.policy.apex.model.policymodel.concepts.AxTask; + +/** + * Dummy task executor for testing. + */ +@NoArgsConstructor +@AllArgsConstructor +public class DummyTaskExecutor extends TaskExecutor { + private static final String EVENT_KEY = "Event1:0.0.1"; + private boolean override; + + @Override + public void prepare() throws StateMachineException { + if (!override) { + super.prepare(); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public Map<String, Map<String, Object>> execute(final long executionId, final Properties executionProperties, + final Map<String, Object> newIncomingFields) throws StateMachineException, ContextException { + if (!override) { + super.execute(executionId, executionProperties, newIncomingFields); + } + + AxArtifactKey eventKey = new AxArtifactKey(EVENT_KEY); + return Map.of(eventKey.getName(), new EnEvent(eventKey)); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxTask getSubject() { + if (!override) { + return super.getSubject(); + } + + AxArtifactKey taskKey = new AxArtifactKey("FirstTask:0.0.1"); + AxTask task = new AxTask(taskKey); + task.setOutputEvents(Map.of("Event1", new AxEvent(new AxArtifactKey(EVENT_KEY)))); + return task; + } + + /** + * {@inheritDoc}. + */ + @Override + public void cleanUp() throws StateMachineException { + if (!override) { + super.cleanUp(); + } + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskSelectExecutor.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskSelectExecutor.java new file mode 100644 index 000000000..a5525ac83 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/DummyTaskSelectExecutor.java @@ -0,0 +1,74 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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 java.util.Properties; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.onap.policy.apex.context.ContextException; +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; + +/** + * Dummy task selection executor for testing. + */ +@NoArgsConstructor +@AllArgsConstructor +public class DummyTaskSelectExecutor extends TaskSelectExecutor { + private boolean override; + + @Setter + private static int taskNo; + + @Override + public void prepare() throws StateMachineException { + if (!override) { + super.prepare(); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public AxArtifactKey execute(final long executionId, final Properties executionProperties, + final EnEvent newIncomingEvent) throws StateMachineException, ContextException { + if (!override) { + return super.execute(executionId, executionProperties, newIncomingEvent); + } + + return new AxArtifactKey("task" + (taskNo++) + ":0.0.1"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void cleanUp() throws StateMachineException { + if (!override) { + super.cleanUp(); + } + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateExecutorTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateExecutorTest.java new file mode 100644 index 000000000..f3e12cc1f --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateExecutorTest.java @@ -0,0 +1,88 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.model.basicmodel.concepts.AxReferenceKey; +import org.onap.policy.apex.model.policymodel.concepts.AxState; + +/** + * Test task executor. + */ +public class StateExecutorTest { + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private AxState axStateMock; + + @Mock + private ExecutorFactory executorFactoryMock; + + @Mock + private Executor<EnEvent, StateOutput, AxState, ApexInternalContext> nextExecutorMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + Mockito.doReturn(new AxReferenceKey("Policy:0.0.1:PolName:State0")).when(axStateMock).getKey(); + } + + @Test + public void testStateExecutor() { + StateExecutor executor = new StateExecutor(executorFactoryMock); + + executor.setContext(null, axStateMock, internalContextMock); + assertEquals("Policy:0.0.1:PolName:State0", executor.getKey().getId()); + assertEquals(null, executor.getParent()); + assertEquals(internalContextMock, executor.getContext()); + assertEquals(null, executor.getNext()); + assertEquals(null, executor.getIncoming()); + assertEquals(null, executor.getOutgoing()); + assertEquals(axStateMock, executor.getSubject()); + + executor.setParameters(new ExecutorParameters()); + executor.setNext(nextExecutorMock); + assertEquals(nextExecutorMock, executor.getNext()); + executor.setNext(null); + assertEquals(null, executor.getNext()); + + assertThatThrownBy(() -> executor.executePre(0, null, null)) + .hasMessage("execution pre work not implemented on class"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessage("execution post work not implemented on class"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutorTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutorTest.java new file mode 100644 index 000000000..8f6544497 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateFinalizerExecutorTest.java @@ -0,0 +1,139 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import java.util.Map; +import java.util.Properties; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.AxReferenceKey; +import org.onap.policy.apex.model.policymodel.concepts.AxState; +import org.onap.policy.apex.model.policymodel.concepts.AxStateFinalizerLogic; + +/** + * Test task executor. + */ +public class StateFinalizerExecutorTest { + @Mock + private Executor<?, ?, ?, ?> parentMock; + + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private AxStateFinalizerLogic stateFinalizerLogicMock; + + @Mock + private Executor<Map<String, Object>, String, AxStateFinalizerLogic, ApexInternalContext> nextExecutorMock; + + @Mock + private EnEvent incomingEvent; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + AxState state = new AxState(); + state.getStateOutputs().put("ValidOutput", null); + + Mockito.doReturn(state).when(parentMock).getSubject(); + + Mockito.doReturn(new AxReferenceKey("State:0.0.1:StateName:StateSFL")).when(stateFinalizerLogicMock).getKey(); + } + + @Test + public void testStateFinalizerExecutor() throws StateMachineException, ContextException { + DummyStateFinalizerExecutor executor = new DummyStateFinalizerExecutor(); + + executor.setContext(parentMock, stateFinalizerLogicMock, internalContextMock); + assertEquals("State:0.0.1:StateName:StateSFL", executor.getKey().getId()); + assertEquals(null, executor.getExecutionContext()); + assertEquals(parentMock, executor.getParent()); + assertEquals(internalContextMock, executor.getContext()); + assertEquals(null, executor.getNext()); + assertEquals(null, executor.getIncoming()); + assertEquals(null, executor.getOutgoing()); + assertEquals(stateFinalizerLogicMock, executor.getSubject()); + + executor.setParameters(new ExecutorParameters()); + executor.setNext(nextExecutorMock); + assertEquals(nextExecutorMock, executor.getNext()); + executor.setNext(null); + assertEquals(null, executor.getNext()); + + assertThatThrownBy(executor::cleanUp) + .hasMessage("cleanUp() not implemented on class"); + Mockito.doReturn(null).when(stateFinalizerLogicMock).getLogic(); + + assertThatThrownBy(executor::prepare) + .hasMessage("state finalizer logic cannot be null."); + Mockito.doReturn("some task logic").when(stateFinalizerLogicMock).getLogic(); + + executor.prepare(); + + executor.executePre(0, new Properties(), incomingEvent); + assertThatThrownBy(() -> executor.executePre(0, null, incomingEvent)) + .hasMessageMatching("^executionProperties is marked .*on.*ull but is null$"); + + executor.executePre(0, new Properties(), incomingEvent); + + assertThatThrownBy(() -> executor.execute(0, new Properties(), incomingEvent)) + .hasMessage("execute() not implemented on abstract StateFinalizerExecutionContext class, " + + "only on its subclasses"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessage("execute-post: state finalizer logic execution failure on state \"NULL:0.0.0:" + + "NULL:NULL\" on finalizer logic null"); + executor.getExecutionContext().setMessage("Execution message"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessage("execute-post: state finalizer logic execution failure on state \"NULL:0.0.0:" + + "NULL:NULL\" on finalizer logic null, user message: Execution message"); + executor.executePre(0, new Properties(), incomingEvent); + + assertThatThrownBy(() -> executor.executePost(true)) + .hasMessage("execute-post: state finalizer logic \"null\" did not select an output state"); + executor.executePre(0, new Properties(), incomingEvent); + + executor.getExecutionContext().setSelectedStateOutputName("ThisOutputDoesNotExist"); + assertThatThrownBy(() -> executor.executePost(true)) + .hasMessage("execute-post: state finalizer logic \"null\" selected output state " + + "\"ThisOutputDoesNotExist\" that does not exsist on state \"NULL:0.0.0:NULL:NULL\""); + executor.executePre(0, new Properties(), incomingEvent); + + executor.getExecutionContext().setSelectedStateOutputName("ValidOutput"); + executor.executePost(true); + + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutorTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutorTest.java new file mode 100644 index 000000000..5d91bd2b3 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/StateMachineExecutorTest.java @@ -0,0 +1,336 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.ContextException; +import org.onap.policy.apex.context.parameters.SchemaParameters; +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.basicmodel.service.ModelService; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchemas; +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.AxPolicy; +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.onap.policy.common.parameters.ParameterService; + +/** + * Test task executor. + */ +public class StateMachineExecutorTest { + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private Executor<EnEvent, Collection<EnEvent>, AxPolicy, ApexInternalContext> nextExecutorMock; + + @Mock + private ExecutorFactory executorFactoryMock; + + @Mock + private EnEvent incomingEventMock; + + private AxPolicy axPolicy = new AxPolicy(); + + private DummyTaskSelectExecutor dummyTsle; + + private DummyStateFinalizerExecutor dummySfle; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + axPolicy.setKey(new AxArtifactKey("Policy:0.0.1")); + + AxReferenceKey state0Key = new AxReferenceKey(axPolicy.getKey(), "state0"); + AxState state0 = new AxState(state0Key); + + AxReferenceKey state1Key = new AxReferenceKey(axPolicy.getKey(), "state1"); + AxState state1 = new AxState(state1Key); + + axPolicy.getStateMap().put("State0", state0); + axPolicy.getStateMap().put("State1", state1); + axPolicy.setFirstState("state0"); + + AxArtifactKey event0Key = new AxArtifactKey("Event0:0.0.1"); + AxEvent event0 = new AxEvent(event0Key, "a.name.space", "source", "target"); + AxArtifactKey event1Key = new AxArtifactKey("Event1:0.0.1"); + AxEvent event1 = new AxEvent(event1Key, "a.name.space", "source", "target"); + AxArtifactKey event2Key = new AxArtifactKey("Event2:0.0.1"); + AxEvent event2 = new AxEvent(event2Key, "a.name.space", "source", "target"); + AxEvents events = new AxEvents(); + events.getEventMap().put(event0Key, event0); + events.getEventMap().put(event1Key, event1); + events.getEventMap().put(event2Key, event2); + ModelService.registerModel(AxEvents.class, events); + + AxReferenceKey fieldKey = new AxReferenceKey("Event1:0.0.1:event:Field0"); + AxArtifactKey stringSchemaKey = new AxArtifactKey("StringSchema:0.0.1"); + AxContextSchema stringSchema = new AxContextSchema(stringSchemaKey, "Java", "java.lang.String"); + AxContextSchemas schemas = new AxContextSchemas(); + schemas.getSchemasMap().put(stringSchemaKey, stringSchema); + ModelService.registerModel(AxContextSchemas.class, schemas); + + AxField event1Field0Definition = new AxField(fieldKey, stringSchemaKey); + event1.getParameterMap().put("Event1Field0", event1Field0Definition); + + event0.getParameterMap().put("Event1Field0", event1Field0Definition); + event0.getParameterMap().put("UnusedField", event1Field0Definition); + + Mockito.doReturn(event0Key).when(incomingEventMock).getKey(); + Mockito.doReturn(event0).when(incomingEventMock).getAxEvent(); + + state0.setTrigger(event0Key); + state1.setTrigger(event1Key); + + AxArtifactKey task0Key = new AxArtifactKey("task0:0.0.1"); + AxTask task0 = new AxTask(task0Key); + + AxArtifactKey task1Key = new AxArtifactKey("task1:0.0.1"); + AxTask task1 = new AxTask(task1Key); + + AxTasks tasks = new AxTasks(); + tasks.getTaskMap().put(task0Key, task0); + tasks.getTaskMap().put(task1Key, task1); + ModelService.registerModel(AxTasks.class, tasks); + + ParameterService.register(new SchemaParameters()); + + AxReferenceKey stateOutput0Key = new AxReferenceKey("Policy:0.0.1:state0:stateOutput0"); + AxStateOutput stateOutput0 = new AxStateOutput(stateOutput0Key, event1Key, state1.getKey()); + + state0.getStateOutputs().put(stateOutput0Key.getLocalName(), stateOutput0); + + AxReferenceKey stateOutput1Key = new AxReferenceKey("Policy:0.0.1:state0:stateOutput1"); + AxStateOutput stateOutput1 = new AxStateOutput(stateOutput1Key, event2Key, AxReferenceKey.getNullKey()); + + state1.getStateOutputs().put(stateOutput1Key.getLocalName(), stateOutput1); + + AxReferenceKey str0Key = new AxReferenceKey("Policy:0.0.1:state0:str0"); + AxStateTaskReference str0 = new AxStateTaskReference(str0Key, AxStateTaskOutputType.DIRECT, stateOutput0Key); + state0.getTaskReferences().put(task0Key, str0); + + AxReferenceKey sflKey = new AxReferenceKey("Policy:0.0.1:state1:sfl"); + AxStateFinalizerLogic sfl = new AxStateFinalizerLogic(sflKey, "Java", "State fianlizer logic"); + state1.getStateFinalizerLogicMap().put("sfl", sfl); + + AxReferenceKey str1Key = new AxReferenceKey("Policy:0.0.1:state1:str1"); + AxStateTaskReference str1 = new AxStateTaskReference(str1Key, AxStateTaskOutputType.LOGIC, sflKey); + state1.getTaskReferences().put(task1Key, str1); + + Mockito.doReturn(new DummyTaskExecutor(true)).when(executorFactoryMock).getTaskExecutor(Mockito.anyObject(), + Mockito.anyObject(), Mockito.anyObject()); + + dummyTsle = new DummyTaskSelectExecutor(true); + Mockito.doReturn(dummyTsle).when(executorFactoryMock).getTaskSelectionExecutor(Mockito.anyObject(), + Mockito.anyObject(), Mockito.anyObject()); + + dummySfle = new DummyStateFinalizerExecutor(true); + Mockito.doReturn(dummySfle).when(executorFactoryMock).getStateFinalizerExecutor(Mockito.anyObject(), + Mockito.anyObject(), Mockito.anyObject()); + } + + @After + public void cleardown() { + ParameterService.clear(); + ModelService.clear(); + } + + @Test + public void testStateMachineExecutor() throws StateMachineException, ContextException { + StateMachineExecutor executor = + new StateMachineExecutor(executorFactoryMock, new AxArtifactKey("OwnerKey:0.0.1")); + + assertThatThrownBy(() -> executor.execute(0, null, incomingEventMock)) + .hasMessage("no states defined on state machine"); + executor.setContext(null, axPolicy, internalContextMock); + assertEquals("Policy:0.0.1", executor.getKey().getId()); + assertNull(executor.getParent()); + assertEquals(internalContextMock, executor.getContext()); + assertNull(executor.getNext()); + assertNull(executor.getIncoming()); + assertTrue(executor.getOutgoing().isEmpty()); + assertEquals(axPolicy, executor.getSubject()); + + executor.setParameters(new ExecutorParameters()); + executor.setNext(nextExecutorMock); + assertEquals(nextExecutorMock, executor.getNext()); + executor.setNext(null); + assertNull(executor.getNext()); + + assertThatThrownBy(() -> executor.executePre(0, null, null)) + .hasMessage("execution pre work not implemented on class"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessage("execution post work not implemented on class"); + assertThatThrownBy(executor::prepare) + .isInstanceOf(NullPointerException.class); + axPolicy.setFirstState("BadState"); + executor.setContext(null, axPolicy, internalContextMock); + assertThatThrownBy(() -> executor.execute(0, null, incomingEventMock)) + .hasMessage("first state not defined on state machine"); + axPolicy.setFirstState("state0"); + executor.setContext(null, axPolicy, internalContextMock); + executor.execute(0, null, incomingEventMock); + + DummyTaskSelectExecutor.setTaskNo(0); + executor.execute(0, null, incomingEventMock); + + AxReferenceKey badStateKey = new AxReferenceKey("Policy:0.0.1:PName:BadState"); + axPolicy.getStateMap().get("State1").getStateOutputs().get("stateOutput1").setNextState(badStateKey); + DummyTaskSelectExecutor.setTaskNo(0); + assertThatThrownBy(() -> executor.execute(0, null, incomingEventMock)) + .hasMessage("state execution failed, next state \"Policy:0.0.1:PName:BadState\" not found"); + axPolicy.getStateMap().get("State1").getStateOutputs().get("stateOutput1") + .setNextState(AxReferenceKey.getNullKey()); + DummyTaskSelectExecutor.setTaskNo(0); + executor.execute(0, null, incomingEventMock); + + axPolicy.getStateMap().get("State1").setTrigger(new AxArtifactKey("BadTrigger:0.0.1")); + DummyTaskSelectExecutor.setTaskNo(0); + assertThatThrownBy(() -> executor.execute(0, null, incomingEventMock)) + .hasMessage("incoming event \"Event1:0.0.1\" does not match trigger \"BadTrigger:0.0.1\" " + + "of state \"Policy:0.0.1:NULL:state1\""); + axPolicy.getStateMap().get("State1").setTrigger(new AxArtifactKey("Event1:0.0.1")); + DummyTaskSelectExecutor.setTaskNo(0); + executor.execute(0, null, incomingEventMock); + + AxStateFinalizerLogic savedSfl = axPolicy.getStateMap().get("State1").getStateFinalizerLogicMap().get("sfl"); + axPolicy.getStateMap().get("State1").getStateFinalizerLogicMap().put("sfl", null); + assertThatThrownBy(() -> executor.setContext(null, axPolicy, internalContextMock)) + .hasMessage("state finalizer logic on task reference " + + "\"AxStateTaskReference:(stateKey=AxReferenceKey:(parentKeyName=Policy," + + "parentKeyVersion=0.0.1,parentLocalName=state1,localName=str1)," + + "outputType=LOGIC,output=AxReferenceKey:(parentKeyName=Policy,parentKeyVersion=0.0.1," + + "parentLocalName=state1,localName=sfl))\" on state \"Policy:0.0.1:NULL:state1\" " + "does not exist"); + axPolicy.getStateMap().get("State1").getStateFinalizerLogicMap().put("sfl", savedSfl); + executor.setContext(null, axPolicy, internalContextMock); + + DummyTaskSelectExecutor.setTaskNo(0); + executor.execute(0, null, incomingEventMock); + + AxArtifactKey task1Key = new AxArtifactKey("task1:0.0.1"); + axPolicy.getStateMap().get("State1").getTaskReferences().get(task1Key) + .setStateTaskOutputType(AxStateTaskOutputType.UNDEFINED); + assertThatThrownBy(() -> executor.setContext(null, axPolicy, internalContextMock)) + .hasMessage("invalid state output type on task reference \"AxStateTaskReference:(stateKey" + + "=AxReferenceKey:(parentKeyName=Policy,parentKeyVersion=0.0.1,parentLocalName=state1,localName=str1)," + + "outputType=UNDEFINED,output=AxReferenceKey:(parentKeyName=Policy," + + "parentKeyVersion=0.0.1,parentLocalName=state1,localName=sfl))\" " + + "on state \"Policy:0.0.1:NULL:state1\""); + axPolicy.getStateMap().get("State1").getTaskReferences().get(task1Key) + .setStateTaskOutputType(AxStateTaskOutputType.LOGIC); + executor.setContext(null, axPolicy, internalContextMock); + + DummyTaskSelectExecutor.setTaskNo(0); + executor.execute(0, null, incomingEventMock); + + DummyTaskSelectExecutor.setTaskNo(0); + dummySfle.setReturnBad(true); + assertThatThrownBy(() -> executor.execute(0, null, incomingEventMock)) + .hasMessage("State execution of state \"Policy:0.0.1:NULL:state1\" on task \"task1:0.0.1\"" + + " failed: state output definition for state output \"stateOutputBad\" not found for " + + "state \"Policy:0.0.1:NULL:state1\""); + DummyTaskSelectExecutor.setTaskNo(0); + dummySfle.setReturnBad(false); + executor.execute(0, null, incomingEventMock); + + assertThatThrownBy(executor::cleanUp) + .hasMessage("cleanUp() not implemented on class"); + } + + @Test + public void testStateOutput() throws StateMachineException { + final StateOutput output = + new StateOutput(axPolicy.getStateMap().get("State0").getStateOutputs().get("stateOutput0")); + assertNotNull(output); + + assertEquals("stateOutput0", output.getStateOutputDefinition().getKey().getLocalName()); + + assertThatThrownBy(() -> output.setEventFields(null, null)) + .hasMessage("incomingFieldDefinitionMap may not be null"); + Map<String, AxEvent> incomingFieldDefinitionMap = new LinkedHashMap<>(); + assertThatThrownBy(() -> output.setEventFields(incomingFieldDefinitionMap, null)) + .hasMessage("eventFieldMaps may not be null"); + Map<String, Map<String, Object>> eventFieldMaps = new LinkedHashMap<>(); + output.setEventFields(incomingFieldDefinitionMap, eventFieldMaps); + AxEvent event = new AxEvent(new AxArtifactKey("Event1", "0.0.1")); + event.setParameterMap(Map.of("key", new AxField())); + incomingFieldDefinitionMap.put("Event1", event); + eventFieldMaps.put("Event1", Map.of("key2", "value")); + assertThatThrownBy(() -> output.setEventFields(incomingFieldDefinitionMap, eventFieldMaps)) + .hasMessage("field definitions and values do not match for event Event1:0.0.1\n[key]\n[key2]"); + + eventFieldMaps.put("Event1", Map.of("key", "value")); + assertThatThrownBy(() -> output.setEventFields(incomingFieldDefinitionMap, eventFieldMaps)) + .hasMessage("field \"key\" does not exist on event \"Event1:0.0.1\""); + + incomingFieldDefinitionMap.clear(); + eventFieldMaps.clear(); + AxArtifactKey stringSchemaKey = new AxArtifactKey("StringSchema:0.0.1"); + AxReferenceKey fieldKey = new AxReferenceKey("Event1:0.0.1:event:Field0"); + AxField event1Field0Definition = new AxField(fieldKey, stringSchemaKey); + event.setParameterMap(Map.of("Event1Field0", event1Field0Definition)); + incomingFieldDefinitionMap.put("Event1", event); + eventFieldMaps.put("Event1", Map.of("Event1Field0", "Value")); + output.setEventFields(incomingFieldDefinitionMap, eventFieldMaps); + + StateOutput outputCopy = new StateOutput(axPolicy.getStateMap().get("State0") + .getStateOutputs().get("stateOutput0")); + + EnEvent incomingEvent = new EnEvent(new AxArtifactKey("Event0:0.0.1")); + outputCopy.copyUnsetFields(incomingEvent); + incomingEvent.put("Event1Field0", "Hello"); + outputCopy.copyUnsetFields(incomingEvent); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskExecutorTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskExecutorTest.java new file mode 100644 index 000000000..4160a9f19 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskExecutorTest.java @@ -0,0 +1,240 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 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; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.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.AxEvent; +import org.onap.policy.apex.model.eventmodel.concepts.AxField; +import org.onap.policy.apex.model.eventmodel.concepts.AxInputField; +import org.onap.policy.apex.model.eventmodel.concepts.AxOutputField; +import org.onap.policy.apex.model.policymodel.concepts.AxTask; +import org.onap.policy.apex.model.policymodel.concepts.AxTaskLogic; +import org.onap.policy.apex.model.policymodel.concepts.AxTaskParameter; + +/** + * Test task excutor. + */ +public class TaskExecutorTest { + @Mock + private AxTask axTaskMock; + + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private AxInputField axInputFieldMock; + + @Mock + private AxInputField axOptionalInputFieldMock; + + @Mock + private AxOutputField axOutputFieldMock; + + @Mock + private AxOutputField axOptionalOutputFieldMock; + + @Mock + private AxOutputField axMissingOutputFieldMock; + + @Mock + private Executor<Map<String, Object>, Map<String, Map<String, Object>>, AxTask, + ApexInternalContext> nextExecutorMock; + + @Mock + private AxTaskLogic taskLogicMock; + + private Map<String, AxField> inFieldMap; + private Map<String, AxField> outFieldMap; + private List<TaskParameters> taskParametersFromConfig; + private Map<String, AxEvent> outEvents = new TreeMap<>(); + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + AxArtifactKey task0Key = new AxArtifactKey("Task0:0.0.1"); + Mockito.doReturn(task0Key).when(axTaskMock).getKey(); + Mockito.doReturn(task0Key.getId()).when(axTaskMock).getId(); + + inFieldMap = Map.of("InField0", axInputFieldMock, "InField1", axOptionalInputFieldMock); + outFieldMap = new LinkedHashMap<>(); + + outFieldMap.put("OutField0", axOutputFieldMock); + outFieldMap.put("OutField1", axOptionalOutputFieldMock); + + AxEvent inEvent = new AxEvent(); + inEvent.setParameterMap(inFieldMap); + AxEvent outEvent = new AxEvent(new AxArtifactKey("outputEvent:1.0.0")); + outEvent.setParameterMap(outFieldMap); + outEvents.put(outEvent.getKey().getName(), outEvent); + + AxArtifactKey schemaKey = new AxArtifactKey("Schema:0.0.1"); + Mockito.doReturn(schemaKey).when(axInputFieldMock).getSchema(); + Mockito.doReturn(schemaKey).when(axOptionalInputFieldMock).getSchema(); + Mockito.doReturn(schemaKey).when(axOutputFieldMock).getSchema(); + Mockito.doReturn(schemaKey).when(axOptionalOutputFieldMock).getSchema(); + Mockito.doReturn(schemaKey).when(axMissingOutputFieldMock).getSchema(); + + Mockito.doReturn(true).when(axOptionalInputFieldMock).getOptional(); + Mockito.doReturn(true).when(axOptionalOutputFieldMock).getOptional(); + Mockito.doReturn(false).when(axMissingOutputFieldMock).getOptional(); + + Mockito.doReturn(taskLogicMock).when(axTaskMock).getTaskLogic(); + + Mockito.doReturn(inEvent).when(axTaskMock).getInputEvent(); + Mockito.doReturn(outEvents).when(axTaskMock).getOutputEvents(); + + Mockito.doReturn(new AxArtifactKey("Context:0.0.1")).when(internalContextMock).getKey(); + + Map<String, AxTaskParameter> taskParameters = new HashMap<>(); + taskParameters.put("parameterKey2", new AxTaskParameter(new AxReferenceKey(), "parameterOriginalValue2")); + Mockito.doReturn(taskParameters).when(axTaskMock).getTaskParameters(); + + taskParametersFromConfig = new ArrayList<>(); + taskParametersFromConfig.add(new TaskParameters("parameterKey0", "parameterNewValue0", "Task0:0.0.1")); + taskParametersFromConfig.add(new TaskParameters("parameterKey1", "parameterNewValue1", "Task1:0.0.1")); + taskParametersFromConfig.add(new TaskParameters("parameterKey2", "parameterNewValue2", null)); + } + + @Test + public void testTaskExecutor() throws StateMachineException, ContextException { + final DummyTaskExecutor executor = new DummyTaskExecutor(); + executor.setContext(null, axTaskMock, internalContextMock); + assertEquals("Task0:0.0.1", executor.getKey().getId()); + assertEquals(null, executor.getExecutionContext()); + assertEquals(null, executor.getParent()); + assertEquals(internalContextMock, executor.getContext()); + assertEquals(null, executor.getNext()); + assertEquals(null, executor.getIncoming()); + assertEquals(null, executor.getOutgoing()); + assertNotNull(executor.getSubject()); + + executor.setParameters(new ExecutorParameters()); + executor.setNext(nextExecutorMock); + assertEquals(nextExecutorMock, executor.getNext()); + executor.setNext(null); + assertEquals(null, executor.getNext()); + + assertThatThrownBy(() -> executor.cleanUp()).hasMessageContaining("cleanUp() not implemented on class"); + + Mockito.doReturn(null).when(taskLogicMock).getLogic(); + + assertThatThrownBy(() -> executor.prepare()).hasMessageContaining("task logic cannot be null."); + + Mockito.doReturn("some task logic").when(taskLogicMock).getLogic(); + + executor.prepare(); + + Map<String, Object> incomingFields = new LinkedHashMap<>(); + + incomingFields.put("InField0", "A Value"); + + executor.executePre(0, new Properties(), incomingFields); + + assertThatThrownBy(() -> executor.execute(0, new Properties(), incomingFields)) + .hasMessageContaining("execute() not implemented on abstract TaskExecutor class, only on its subclasses"); + + assertThatThrownBy(() -> executor.executePost(false)).hasMessageContaining( + "execute-post: task logic execution failure on task \"Task0\" in model Context:0.0.1"); + + executor.getExecutionContext().setMessage("Execution message"); + + assertThatThrownBy(() -> executor.executePost(false)).hasMessageContaining( + "execute-post: task logic execution failure on task \"Task0\" in model Context:0.0.1, " + + "user message: Execution message"); + + executor.executePost(true); + + outFieldMap.put("MissingField", axMissingOutputFieldMock); + outEvents.get("outputEvent").getParameterMap().put("MissingField", axMissingOutputFieldMock); + assertThatThrownBy(() -> executor.executePost(true)).hasMessageContaining( + "Fields for task output events \"[outputEvent]\" are missing for task \"Task0:0.0.1\""); + + outFieldMap.remove("MissingField"); + outEvents.get("outputEvent").getParameterMap().remove("MissingField"); + executor.getExecutionContext().outFields.put("BadExtraField", "Howdy!"); + + assertThatThrownBy(() -> executor.executePost(true)).hasMessageContaining( + "task output event \"[outputEvent]\" contains fields that are unwanted for task \"Task0:0.0.1\""); + + executor.getExecutionContext().outFields.remove("BadExtraField"); + outFieldMap.put("InField1", axMissingOutputFieldMock); + executor.executePost(true); + + outFieldMap.put("InField0", axMissingOutputFieldMock); + executor.executePost(true); + + executor.getExecutionContext().outFields.put("InField0", "Output Value"); + outEvents.get("outputEvent").getParameterMap().put("InField0", axMissingOutputFieldMock); + executor.executePost(true); + + executor.getExecutionContext().outFields.remove("InField0"); + executor.executePost(true); + + assertThatThrownBy(() -> executor.executePre(0, null, incomingFields)) + .hasMessageMatching("^executionProperties is marked .*on.*ull but is null$"); + } + + @Test + public void testTaskExecutorForTaskParameters() { + DummyTaskExecutor executorForParmeterTest = new DummyTaskExecutor(false); + + executorForParmeterTest.setContext(null, axTaskMock, internalContextMock); + executorForParmeterTest.updateTaskParameters(taskParametersFromConfig); + assertNotNull(executorForParmeterTest.getSubject().getTaskParameters()); + // taskId matched, parameter value updated with the new value + assertEquals("parameterNewValue0", + executorForParmeterTest.getSubject().getTaskParameters().get("parameterKey0").getTaskParameterValue()); + // taskId mismatch, so the parameter is not updated in the task + assertNull(executorForParmeterTest.getSubject().getTaskParameters().get("parameterKey1")); + // taskId is not available, so parameter is updated in the task + assertEquals("parameterNewValue2", + executorForParmeterTest.getSubject().getTaskParameters().get("parameterKey2").getTaskParameterValue()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutorTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutorTest.java new file mode 100644 index 000000000..613d1ae01 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/TaskSelectExecutorTest.java @@ -0,0 +1,148 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.AxState; +import org.onap.policy.apex.model.policymodel.concepts.AxStateTaskReference; +import org.onap.policy.apex.model.policymodel.concepts.AxTaskSelectionLogic; + +/** + * Test task executor. + */ +public class TaskSelectExecutorTest { + @Mock + private AxState axStateMock; + + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private Executor<EnEvent, AxArtifactKey, AxState, ApexInternalContext> nextExecutorMock; + + @Mock + private AxTaskSelectionLogic taskSelectionLogicMock; + + @Mock + private EnEvent incomingEvent; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + AxReferenceKey state0Key = new AxReferenceKey("State0Parent:0.0.1:Parent:State0"); + Mockito.doReturn(state0Key).when(axStateMock).getKey(); + Mockito.doReturn(state0Key.getId()).when(axStateMock).getId(); + + Map<AxArtifactKey, AxStateTaskReference> taskReferences = new LinkedHashMap<>(); + taskReferences.put(new AxArtifactKey("Task0:0.0.0"), null); + taskReferences.put(new AxArtifactKey("Task1:0.0.0"), null); + Mockito.doReturn(taskReferences).when(axStateMock).getTaskReferences(); + Mockito.doReturn(new AxArtifactKey("Task1:0.0.0")).when(axStateMock).getDefaultTask(); + + Mockito.doReturn(taskSelectionLogicMock).when(axStateMock).getTaskSelectionLogic(); + + Mockito.doReturn(new AxArtifactKey("Context:0.0.1")).when(internalContextMock).getKey(); + } + + @Test + public void testTaskSelectionExecutor() throws StateMachineException { + DummyTaskSelectExecutor executor = new DummyTaskSelectExecutor(); + + executor.setContext(null, axStateMock, internalContextMock); + assertEquals("State0Parent:0.0.1:Parent:State0", executor.getKey().getId()); + assertEquals(null, executor.getExecutionContext()); + assertEquals(null, executor.getParent()); + assertEquals(internalContextMock, executor.getContext()); + assertEquals(null, executor.getNext()); + assertEquals(null, executor.getIncoming()); + assertEquals(null, executor.getOutgoing()); + assertEquals(axStateMock, executor.getSubject()); + + executor.setParameters(new ExecutorParameters()); + executor.setNext(nextExecutorMock); + assertEquals(nextExecutorMock, executor.getNext()); + executor.setNext(null); + assertEquals(null, executor.getNext()); + + assertThatThrownBy(executor::cleanUp) + .hasMessage("cleanUp() not implemented on class"); + Mockito.doReturn(null).when(taskSelectionLogicMock).getLogic(); + + assertThatThrownBy(executor::prepare) + .hasMessage("task selection logic cannot be null."); + Mockito.doReturn("some task logic").when(taskSelectionLogicMock).getLogic(); + + executor.prepare(); + + executor.executePre(0, new Properties(), incomingEvent); + + assertThatThrownBy(() -> executor.execute(0, new Properties(), incomingEvent)) + .hasMessage("execute() not implemented on class"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessage("execute-post: task selection logic failed on state \"State0Parent:0.0.1:Parent:State0\""); + + executor.getExecutionContext().setMessage("Execution message"); + assertThatThrownBy(() -> executor.executePost(false)) + .hasMessageContaining("execute-post: task selection logic failed on state \"" + + "State0Parent:0.0.1:Parent:State0\", user message: Execution message"); + executor.executePre(0, new Properties(), incomingEvent); + + executor.executePost(true); + assertEquals("Task1", executor.getOutgoing().getName()); + + executor.executePre(0, new Properties(), incomingEvent); + + executor.getOutgoing().setName("IDontExist"); + assertThatThrownBy(() -> executor.executePost(true)) + .hasMessageContaining("task \"IDontExist:0.0.0\" returned by task selection logic not defined " + + "on state \"State0Parent:0.0.1:Parent:State0\""); + executor.executePre(0, new Properties(), incomingEvent); + + executor.getOutgoing().setName("Task0"); + + executor.executePost(true); + assertEquals("Task0", executor.getOutgoing().getName()); + + assertThatThrownBy(() -> executor.executePre(0, null, incomingEvent)) + .hasMessageMatching("^executionProperties is marked .*on.*ull but is null$"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacadeTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacadeTest.java new file mode 100644 index 000000000..68d478877 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxStateFacadeTest.java @@ -0,0 +1,86 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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.context; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.AxTask; +import org.onap.policy.apex.model.policymodel.concepts.AxTasks; + +/** + * Test the state facade. + */ +public class AxStateFacadeTest { + @Mock + private AxState axStateMock; + + @Mock + private AxTask axTaskMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + AxReferenceKey stateKey = new AxReferenceKey("StateParent:0.0.1:ParentName:StateName"); + Mockito.doReturn(stateKey).when(axStateMock).getKey(); + + AxArtifactKey task0Key = new AxArtifactKey("Task0:0.0.1"); + Mockito.doReturn(task0Key).when(axStateMock).getDefaultTask(); + + Map<AxArtifactKey, Object> taskReferences = new LinkedHashMap<>();; + taskReferences.put(task0Key, null); + Mockito.doReturn(taskReferences).when(axStateMock).getTaskReferences(); + + AxTasks tasks = new AxTasks(); + tasks.getTaskMap().put(task0Key, axTaskMock); + + ModelService.registerModel(AxTasks.class, tasks); + Mockito.doReturn(task0Key).when(axTaskMock).getKey(); + } + + @Test + public void testAxStateFacade() { + AxStateFacade stateFacade = new AxStateFacade(axStateMock); + + assertEquals("StateName", stateFacade.getStateName()); + assertEquals("StateParent:0.0.1:ParentName:StateName", stateFacade.getId()); + + assertEquals("Task0", stateFacade.getDefaultTaskKey().getName()); + assertNull(stateFacade.getTaskKey(null)); + assertEquals("Task0", stateFacade.getTaskKey("Task0").getName()); + stateFacade.getTaskNames(); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacadeTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacadeTest.java new file mode 100644 index 000000000..9da8ecfad --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/AxTaskFacadeTest.java @@ -0,0 +1,140 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 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.context; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import java.util.TreeMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.parameters.ContextParameterConstants; +import org.onap.policy.apex.context.parameters.SchemaParameters; +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.contextmodel.concepts.AxContextSchema; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchemas; +import org.onap.policy.apex.model.eventmodel.concepts.AxEvent; +import org.onap.policy.apex.model.eventmodel.concepts.AxField; +import org.onap.policy.apex.model.eventmodel.concepts.AxInputField; +import org.onap.policy.apex.model.eventmodel.concepts.AxOutputField; +import org.onap.policy.apex.model.policymodel.concepts.AxTask; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the state facade. + */ +public class AxTaskFacadeTest { + @Mock + private AxTask axTaskMock; + + @Mock + private AxInputField axInputFieldMock; + + @Mock + private AxInputField axInputFieldBadMock; + + @Mock + private AxOutputField axOutputFieldMock; + + @Mock + private AxOutputField axOutputFieldBadMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + AxContextSchemas schemas = new AxContextSchemas(); + + AxArtifactKey stringTypeKey = new AxArtifactKey("StringType:0.0.1"); + AxContextSchema stringType = new AxContextSchema(stringTypeKey, "Java", "java.lang.String"); + schemas.getSchemasMap().put(stringTypeKey, stringType); + + ModelService.registerModel(AxContextSchemas.class, schemas); + + MockitoAnnotations.initMocks(this); + + AxArtifactKey task0Key = new AxArtifactKey("Task0:0.0.1"); + Mockito.doReturn(task0Key).when(axTaskMock).getKey(); + Mockito.doReturn(task0Key.getId()).when(axTaskMock).getId(); + + Map<String, AxField> inFieldMap = Map.of("InField0", axInputFieldMock, "InFieldBad", axInputFieldBadMock); + Map<String, AxField> outFieldMap = Map.of("OutField0", axOutputFieldMock, "OutFieldBad", axOutputFieldBadMock); + + AxEvent inEvent = new AxEvent(); + inEvent.setParameterMap(inFieldMap); + AxEvent outEvent = new AxEvent(new AxArtifactKey("outputEvent:1.0.0")); + outEvent.setParameterMap(outFieldMap); + Map<String, AxEvent> outEvents = new TreeMap<>(); + outEvents.put(outEvent.getKey().getName(), outEvent); + + Mockito.doReturn(inEvent).when(axTaskMock).getInputEvent(); + Mockito.doReturn(outEvents).when(axTaskMock).getOutputEvents(); + + Mockito.doReturn(new AxReferenceKey(task0Key, "InField0")).when(axInputFieldMock).getKey(); + Mockito.doReturn(stringTypeKey).when(axInputFieldMock).getSchema(); + + Mockito.doReturn(new AxReferenceKey(task0Key, "OutField0")).when(axOutputFieldMock).getKey(); + Mockito.doReturn(stringTypeKey).when(axOutputFieldMock).getSchema(); + + ParameterService.register(new SchemaParameters()); + } + + @After + public void teardown() { + ParameterService.deregister(ContextParameterConstants.SCHEMA_GROUP_NAME); + ModelService.clear(); + } + + @Test + public void testAxStateFacade() { + AxTaskFacade taskFacade = new AxTaskFacade(axTaskMock); + + assertEquals("Task0", taskFacade.getTaskName()); + assertEquals("Task0:0.0.1", taskFacade.getId()); + + assertThatThrownBy(() -> taskFacade.getInFieldSchemaHelper("InFieldDoesntExist")) + .hasMessage("no incoming field with name \"InFieldDoesntExist\" " + "defined on task " + + "\"Task0:0.0.1\""); + assertThatThrownBy(() -> taskFacade.getOutFieldSchemaHelper("OutFieldDoesntExist")) + .hasMessage("no outgoing field with name \"OutFieldDoesntExist\" " + "defined on task " + + "\"Task0:0.0.1\""); + assertNotNull(taskFacade.getInFieldSchemaHelper("InField0")); + assertNotNull(taskFacade.getOutFieldSchemaHelper("OutField0")); + + assertThatThrownBy(() -> taskFacade.getInFieldSchemaHelper("InFieldBad")) + .hasMessage("schema helper cannot be created for task field \"InFieldBad\" " + + "with key \"null\" with schema \"null\""); + assertThatThrownBy(() -> taskFacade.getOutFieldSchemaHelper("OutFieldBad")) + .hasMessage("schema helper cannot be created for task field \"OutFieldBad\" " + + "with key \"null\" with schema \"null\""); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/DummyContextAlbum.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/DummyContextAlbum.java new file mode 100644 index 000000000..efb53a682 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/DummyContextAlbum.java @@ -0,0 +1,226 @@ +/*- + * ============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.executor.context; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.NotImplementedException; +import org.onap.policy.apex.context.ContextAlbum; +import org.onap.policy.apex.context.ContextException; +import org.onap.policy.apex.context.SchemaHelper; +import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey; +import org.onap.policy.apex.model.basicmodel.concepts.AxConcept; +import org.onap.policy.apex.model.contextmodel.concepts.AxContextAlbum; + +/** + * Dummy context album for testing. + */ +@AllArgsConstructor +public class DummyContextAlbum implements ContextAlbum { + private final AxArtifactKey key; + + /** + * {@inheritDoc}. + */ + @Override + public void clear() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public boolean containsKey(Object key) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public boolean containsValue(Object value) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Set<Entry<String, Object>> entrySet() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Object get(Object key) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public boolean isEmpty() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Set<String> keySet() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Object put(String key, Object value) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void putAll(Map<? extends String, ? extends Object> map) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Object remove(Object key) { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public int size() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public Collection<Object> values() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxArtifactKey getKey() { + return key; + } + + /** + * {@inheritDoc}. + */ + @Override + public String getName() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxContextAlbum getAlbumDefinition() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public SchemaHelper getSchemaHelper() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void lockForReading(String key) throws ContextException { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void lockForWriting(String key) throws ContextException { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void unlockForReading(String key) throws ContextException { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void unlockForWriting(String key) throws ContextException { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public AxConcept[] getUserArtifactStack() { + throw new NotImplementedException("Not implemented on dummy class"); + } + + /** + * {@inheritDoc}. + */ + @Override + public void setUserArtifactStack(AxConcept[] userArtifactStack) { + // Do nothing + } + + /** + * {@inheritDoc}. + */ + @Override + public void flush() throws ContextException { + throw new NotImplementedException("Not implemented on dummy class"); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContextTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContextTest.java new file mode 100644 index 000000000..c540f51ad --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/StateFinalizerExecutionContextTest.java @@ -0,0 +1,109 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.context; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.ContextAlbum; +import org.onap.policy.apex.core.engine.context.ApexInternalContext; +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.AxReferenceKey; +import org.onap.policy.apex.model.policymodel.concepts.AxState; + +/** + * Test Task Execution Context. + */ +public class StateFinalizerExecutionContextTest { + @Mock + private StateFinalizerExecutor stateFinalizerExecutorMock; + + @Mock + private StateFinalizerExecutor parentExecutorMock; + + @Mock + private AxState axStateMock; + + @Mock + private ApexInternalContext internalContextMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + Set<AxArtifactKey> contextAlbumReferences = new LinkedHashSet<>(); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey0:0.0.1"))); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey1:0.0.1"))); + + Mockito.doReturn(contextAlbumReferences).when(axStateMock).getContextAlbumReferences(); + Mockito.doReturn(new AxReferenceKey("Parent:0.0.1:ParentName:StateName")).when(axStateMock).getKey(); + + Map<AxArtifactKey, ContextAlbum> contextAlbumMap = new LinkedHashMap<>(); + AxArtifactKey album0Key = new AxArtifactKey("AlbumKey0:0.0.1"); + AxArtifactKey album1Key = new AxArtifactKey("AlbumKey1:0.0.1"); + + contextAlbumMap.put(album0Key, new DummyContextAlbum(album0Key)); + contextAlbumMap.put(album1Key, new DummyContextAlbum(album1Key)); + + Mockito.doReturn(contextAlbumMap).when(internalContextMock).getContextAlbums(); + + Mockito.doReturn(parentExecutorMock).when(stateFinalizerExecutorMock).getParent(); + Mockito.doReturn(new AxReferenceKey("Parent:0.0.1:ParentName:LocalName")).when(parentExecutorMock).getKey(); + } + + @Test + public void test() { + final Map<String, Object> fields = new LinkedHashMap<>(); + final Set<String> stateOutputNames = new LinkedHashSet<>(); + + StateFinalizerExecutionContext sfec = new StateFinalizerExecutionContext(stateFinalizerExecutorMock, 0, null, + axStateMock, fields, stateOutputNames, internalContextMock); + + assertNotNull(sfec); + sfec.setMessage("SFEC Message"); + assertEquals("SFEC Message", sfec.getMessage()); + + sfec.setSelectedStateOutputName("SomeOutput"); + assertEquals("SomeOutput", sfec.getSelectedStateOutputName()); + + ContextAlbum contextAlbum = sfec.getContextAlbum("AlbumKey0"); + assertEquals("AlbumKey0:0.0.1", contextAlbum.getKey().getId()); + + assertThatThrownBy(() -> sfec.getContextAlbum("AlbumKeyNonExistant")) + .hasMessage("cannot find definition of context album \"AlbumKeyNonExistant\" " + + "on state \"Parent:0.0.1:ParentName:StateName\""); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContextTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContextTest.java new file mode 100644 index 000000000..24c504822 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskExecutionContextTest.java @@ -0,0 +1,118 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 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.context; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.ContextAlbum; +import org.onap.policy.apex.core.engine.context.ApexInternalContext; +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.AxReferenceKey; +import org.onap.policy.apex.model.policymodel.concepts.AxTask; +import org.onap.policy.apex.model.policymodel.concepts.AxTaskParameter; + +/** + * Test Task Execution Context. + */ +public class TaskExecutionContextTest { + @Mock + private TaskExecutor taskExecutorMock; + + @Mock + private TaskExecutor parentExecutorMock; + + @Mock + private AxTask axTaskMock; + + @Mock + private ApexInternalContext internalContextMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + Set<AxArtifactKey> contextAlbumReferences = new LinkedHashSet<>(); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey0:0.0.1"))); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey1:0.0.1"))); + + Mockito.doReturn(contextAlbumReferences).when(axTaskMock).getContextAlbumReferences(); + + Map<String, AxTaskParameter> taskParameters = new HashMap<>(); + taskParameters.put("parameterKey1", new AxTaskParameter(new AxReferenceKey(), "parameterValue1")); + taskParameters.put("parameterKey2", new AxTaskParameter(new AxReferenceKey(), "parameterValue2")); + Mockito.doReturn(taskParameters).when(axTaskMock).getTaskParameters(); + + Map<AxArtifactKey, ContextAlbum> contextAlbumMap = new LinkedHashMap<>(); + AxArtifactKey album0Key = new AxArtifactKey("AlbumKey0:0.0.1"); + AxArtifactKey album1Key = new AxArtifactKey("AlbumKey1:0.0.1"); + + contextAlbumMap.put(album0Key, new DummyContextAlbum(album0Key)); + contextAlbumMap.put(album1Key, new DummyContextAlbum(album1Key)); + + Mockito.doReturn(contextAlbumMap).when(internalContextMock).getContextAlbums(); + + Mockito.doReturn(parentExecutorMock).when(taskExecutorMock).getParent(); + Mockito.doReturn(new AxArtifactKey("Parent:0.0.1")).when(parentExecutorMock).getKey(); + } + + @Test + public void test() { + final Map<String, Object> inFields = new LinkedHashMap<>(); + final List<Map<String, Object>> outFieldsList = new LinkedList<>(); + + TaskExecutionContext tec = new TaskExecutionContext(taskExecutorMock, 0, null, axTaskMock, inFields, + outFieldsList, internalContextMock); + + assertNotNull(tec); + tec.setMessage("TEC Message"); + assertEquals("TEC Message", tec.getMessage()); + + ContextAlbum contextAlbum = tec.getContextAlbum("AlbumKey0"); + assertEquals("AlbumKey0:0.0.1", contextAlbum.getKey().getId()); + + Map<String, String> parameters = tec.getParameters(); + assertEquals("parameterValue1", parameters.get("parameterKey1")); + assertEquals("parameterValue2", parameters.get("parameterKey2")); + + assertThatThrownBy(() -> tec.getContextAlbum("AlbumKeyNonExistant")) + .hasMessageContaining("cannot find definition of context album \"AlbumKeyNonExistant\" on task \"null\""); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContextTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContextTest.java new file mode 100644 index 000000000..5857e0513 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/context/TaskSelectionExecutionContextTest.java @@ -0,0 +1,109 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.context; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.onap.policy.apex.context.ContextAlbum; +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.TaskSelectExecutor; +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; + +/** + * Test Task Execution Context. + */ +public class TaskSelectionExecutionContextTest { + @Mock + private TaskSelectExecutor taskSelectExecutorMock; + + @Mock + private TaskSelectExecutor parentExecutorMock; + + @Mock + private AxState axStateMock; + + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private EnEvent incomingEventMock; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + Set<AxArtifactKey> contextAlbumReferences = new LinkedHashSet<>(); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey0:0.0.1"))); + contextAlbumReferences.add(new AxArtifactKey(("AlbumKey1:0.0.1"))); + + Mockito.doReturn(contextAlbumReferences).when(axStateMock).getContextAlbumReferences(); + Mockito.doReturn(new AxReferenceKey("Parent:0.0.1:ParentName:StateName")).when(axStateMock).getKey(); + + Map<AxArtifactKey, ContextAlbum> contextAlbumMap = new LinkedHashMap<>(); + AxArtifactKey album0Key = new AxArtifactKey("AlbumKey0:0.0.1"); + AxArtifactKey album1Key = new AxArtifactKey("AlbumKey1:0.0.1"); + + contextAlbumMap.put(album0Key, new DummyContextAlbum(album0Key)); + contextAlbumMap.put(album1Key, new DummyContextAlbum(album1Key)); + + Mockito.doReturn(contextAlbumMap).when(internalContextMock).getContextAlbums(); + + Mockito.doReturn(parentExecutorMock).when(taskSelectExecutorMock).getParent(); + Mockito.doReturn(new AxReferenceKey("Parent:0.0.1:ParentName:LocalName")).when(parentExecutorMock).getKey(); + } + + @Test + public void test() { + final AxArtifactKey outgoingEventKey = new AxArtifactKey("OutEvent:0.0.1"); + + TaskSelectionExecutionContext tsec = new TaskSelectionExecutionContext(taskSelectExecutorMock, 0, axStateMock, + incomingEventMock, outgoingEventKey, internalContextMock); + + assertNotNull(tsec); + tsec.setMessage("TSEC Message"); + assertEquals("TSEC Message", tsec.getMessage()); + + ContextAlbum contextAlbum = tsec.getContextAlbum("AlbumKey0"); + assertEquals("AlbumKey0:0.0.1", contextAlbum.getKey().getId()); + + assertThatThrownBy(() -> tsec.getContextAlbum("AlbumKeyNonExistant")) + .hasMessage("cannot find definition of context album \"AlbumKeyNonExistant\" " + + "on state \"Parent:0.0.1:ParentName:StateName\""); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeExceptionTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeExceptionTest.java new file mode 100644 index 000000000..84f330308 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/exception/StateMachineRuntimeExceptionTest.java @@ -0,0 +1,42 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 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 static org.junit.Assert.assertEquals; + +import java.io.IOException; +import org.junit.Test; + +/** + * Test state machine runtime exception. + * + */ +public class StateMachineRuntimeExceptionTest { + + @Test + public void testException() { + StateMachineRuntimeException smre = new StateMachineRuntimeException("Exception Message"); + assertEquals("Exception Message", smre.getMessage()); + + smre = new StateMachineRuntimeException("Exception Message", new IOException()); + assertEquals("Exception Message", smre.getMessage()); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/engine/executor/impl/ExceutorFactoryImplTest.java b/core/src/test/java/org/onap/policy/apex/core/engine/executor/impl/ExceutorFactoryImplTest.java new file mode 100644 index 000000000..5957ff347 --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/engine/executor/impl/ExceutorFactoryImplTest.java @@ -0,0 +1,240 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.impl; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +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.exception.StateMachineException; +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.apex.model.policymodel.concepts.AxTaskLogic; +import org.onap.policy.apex.model.policymodel.concepts.AxTaskSelectionLogic; +import org.onap.policy.common.parameters.ParameterService; + +/** + * Test the executor factory implementation. + * + */ +public class ExceutorFactoryImplTest { + @Mock + private ApexInternalContext internalContextMock; + + @Mock + private AxState stateMock; + + @Mock + private AxTaskSelectionLogic tslMock; + + @Mock + private AxTask taskMock; + + @Mock + private AxTaskLogic tlMock; + + @Mock + private AxStateFinalizerLogic sflMock; + + @Mock + private Executor<?, ?, ?, ?> parentMock; + + private ExecutorParameters executorPars; + + /** + * Set up mocking. + */ + @Before + public void startMocking() { + MockitoAnnotations.initMocks(this); + + Mockito.doReturn(tslMock).when(stateMock).getTaskSelectionLogic(); + Mockito.doReturn("Dummy").when(tslMock).getLogicFlavour(); + + Mockito.doReturn(tlMock).when(taskMock).getTaskLogic(); + Mockito.doReturn("Dummy").when(tlMock).getLogicFlavour(); + + Mockito.doReturn("Dummy").when(sflMock).getLogicFlavour(); + } + + @After + public void clearPars() { + ParameterService.clear(); + } + + @Test + public void testExecutorFactoryImplGood() throws StateMachineException { + setGoodPars(); + + ExecutorFactoryImpl factory = null; + + factory = new ExecutorFactoryImpl(); + + Mockito.doReturn(true).when(stateMock).checkSetTaskSelectionLogic(); + assertNotNull(factory.getTaskSelectionExecutor(null, stateMock, internalContextMock)); + Mockito.doReturn(false).when(stateMock).checkSetTaskSelectionLogic(); + assertNull(factory.getTaskSelectionExecutor(null, stateMock, internalContextMock)); + + assertNotNull(factory.getTaskExecutor(null, taskMock, internalContextMock)); + + assertNotNull(factory.getStateFinalizerExecutor(parentMock, sflMock, internalContextMock)); + } + + @Test + public void testExecutorFactoryImplNonExistant() { + setNonExistantPars(); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Apex executor class not found for executor plugin " + + "\"org.onap.policy.apex.core.engine.executor.BadTaskExecutor\""); + executorPars.setTaskExecutorPluginClass(null); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Apex executor class not found for executor plugin " + + "\"org.onap.policy.apex.core.engine.executor.BadTaskSelectExecutor\""); + executorPars.setTaskExecutorPluginClass("org.onap.policy.apex.core.engine.executor.DummyTaskExecutor"); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Apex executor class not found for executor plugin " + + "\"org.onap.policy.apex.core.engine.executor.BadTaskSelectExecutor\""); + executorPars.setTaskSelectionExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.DummyTaskSelectExecutor"); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Apex executor class not found for executor plugin " + + "\"org.onap.policy.apex.core.engine.executor.BadStateFinalizerExecutor\""); + } + + @Test + public void testExecutorFactoryImplBad() { + setBadPars(); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Specified Apex executor plugin class \"java.lang.String\" " + + "does not implment the Executor interface"); + executorPars.setTaskExecutorPluginClass("org.onap.policy.apex.core.engine.executor.DummyTaskExecutor"); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Specified Apex executor plugin class \"java.lang.String\" " + + "does not implment the Executor interface"); + executorPars.setTaskSelectionExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.DummyTaskSelectExecutor"); + + assertThatThrownBy(() -> new ExecutorFactoryImpl()) + .hasMessage("Specified Apex executor plugin class \"java.lang.String\" " + + "does not implment the Executor interface"); + } + + @Test + public void testExecutorFactoryCreateErrors() throws StateMachineException { + setGoodPars(); + + executorPars.setTaskExecutorPluginClass(null); + + final ExecutorFactoryImpl factory = new ExecutorFactoryImpl(); + + Mockito.doReturn(true).when(stateMock).checkSetTaskSelectionLogic(); + + assertThatThrownBy(() -> factory.getTaskExecutor(null, taskMock, internalContextMock)) + .hasMessage("Executor plugin class not defined for \"Dummy\" executor of type " + + "\"org.onap.policy.apex.core.engine.executor.TaskExecutor\""); + executorPars.setTaskExecutorPluginClass("org.onap.policy.apex.core.engine.executor.DummyFailingTaskExecutor"); + + ExecutorFactoryImpl factoryInitError = new ExecutorFactoryImpl(); + + assertThatThrownBy(() -> factoryInitError.getTaskExecutor(null, taskMock, internalContextMock)) + .hasMessage("Instantiation error on \"Dummy\" executor of type " + + "\"org.onap.policy.apex.core.engine.executor.DummyFailingTaskExecutor\""); + executorPars.setTaskExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.DummyStateFinalizerExecutor"); + + ExecutorFactoryImpl factoryDummyError = new ExecutorFactoryImpl(); + + assertThatThrownBy(() -> factoryDummyError.getTaskExecutor(null, taskMock, internalContextMock)) + .hasMessage("Executor on \"Dummy\" " + + "of type \"class org.onap.policy.apex.core.engine.executor.DummyStateFinalizerExecutor\"" + + " is not an instance of \"org.onap.policy.apex.core.engine.executor.TaskExecutor\""); + } + + /** + * Set up good parameters. + */ + private void setGoodPars() { + executorPars = new ExecutorParameters(); + executorPars.setTaskExecutorPluginClass("org.onap.policy.apex.core.engine.executor.DummyTaskExecutor"); + executorPars.setTaskSelectionExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.DummyTaskSelectExecutor"); + executorPars.setStateFinalizerExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.DummyStateFinalizerExecutor"); + + EngineParameters enginePars = new EngineParameters(); + enginePars.getExecutorParameterMap().put("Dummy", executorPars); + + ParameterService.register(enginePars); + ParameterService.register(executorPars); + } + + /** + * Set up non existant parameters. + */ + private void setNonExistantPars() { + executorPars = new ExecutorParameters(); + executorPars.setTaskExecutorPluginClass("org.onap.policy.apex.core.engine.executor.BadTaskExecutor"); + executorPars.setTaskSelectionExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.BadTaskSelectExecutor"); + executorPars.setStateFinalizerExecutorPluginClass( + "org.onap.policy.apex.core.engine.executor.BadStateFinalizerExecutor"); + + EngineParameters enginePars = new EngineParameters(); + enginePars.getExecutorParameterMap().put("Dummy", executorPars); + + ParameterService.register(enginePars, true); + ParameterService.register(executorPars, true); + } + + /** + * Set up bad parameters. + */ + private void setBadPars() { + executorPars = new ExecutorParameters(); + executorPars.setTaskExecutorPluginClass("java.lang.String"); + executorPars.setTaskSelectionExecutorPluginClass("java.lang.String"); + executorPars.setStateFinalizerExecutorPluginClass("java.lang.String"); + + EngineParameters enginePars = new EngineParameters(); + enginePars.getExecutorParameterMap().put("Dummy", executorPars); + + ParameterService.register(enginePars, true); + ParameterService.register(executorPars, true); + } +} diff --git a/core/src/test/java/org/onap/policy/apex/core/infrastructure/threading/ThreadingTest.java b/core/src/test/java/org/onap/policy/apex/core/infrastructure/threading/ThreadingTest.java new file mode 100644 index 000000000..35ad9386a --- /dev/null +++ b/core/src/test/java/org/onap/policy/apex/core/infrastructure/threading/ThreadingTest.java @@ -0,0 +1,86 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * Modifications Copyright (C) 2020 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.infrastructure.threading; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; + +/** + * The Class ThreadingTest. + * + * @author Liam Fallon (liam.fallon@ericsson.com) + */ +public class ThreadingTest { + + private static final String LOCAL_NAME = "localName"; + // Logger for this class + private static final XLogger logger = XLoggerFactory.getXLogger(ThreadingTest.class); + + /** + * Test thread factory initialization. + */ + @Test + public void testThreadFactoryInitialization() { + final ApplicationThreadFactory objUnderTest = new ApplicationThreadFactory(LOCAL_NAME, 0); + assertNotNull("Failed to create ApplicationThreadFactory threadFactory0", objUnderTest); + logger.debug(objUnderTest.toString()); + assertTrue("Failed to name ApplicationThreadFactory threadFactory0", + objUnderTest.getName().startsWith("Apex-" + LOCAL_NAME)); + + final ApplicationThreadFactory objUnderTest1 = new ApplicationThreadFactory(LOCAL_NAME, 0); + assertNotNull("Failed to create ApplicationThreadFactory threadFactory1", objUnderTest1); + logger.debug(objUnderTest1.toString()); + assertTrue("Failed to name ApplicationThreadFactory threadFactory1", + objUnderTest1.getName().startsWith("Apex-" + LOCAL_NAME)); + + testThreadFactory(objUnderTest); + testThreadFactory(objUnderTest1); + } + + /** + * Test thread factory. + * + * @param threadFactory the thread factory + */ + private void testThreadFactory(final ApplicationThreadFactory threadFactory) { + final List<Thread> threadList = new ArrayList<>(); + + for (int i = 0; i < 5; i++) { + final Thread thread = threadFactory.newThread(() -> { + }); + threadList.add(thread); + thread.start(); + } + + for (int i = 0; i < 5; i++) { + Thread thread = threadList.get(i); + assertTrue(thread.getName().startsWith("Apex-" + LOCAL_NAME)); + assertTrue(thread.getName().contains(":" + i)); + } + } +} |