aboutsummaryrefslogtreecommitdiffstats
path: root/core/core-engine/src/main/java/org/onap/policy/apex/core/engine/engine/impl/StateMachineHandler.java
blob: c173d1f094e7ff219b91e54921d840d50583cd97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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()<-");
    }
}