2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.plugins.executor.javascript;
23 import java.util.concurrent.BlockingQueue;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.LinkedBlockingQueue;
26 import java.util.concurrent.atomic.AtomicReference;
28 import org.apache.commons.lang3.StringUtils;
29 import org.mozilla.javascript.Context;
30 import org.mozilla.javascript.Script;
31 import org.mozilla.javascript.Scriptable;
32 import org.onap.policy.apex.core.engine.executor.exception.StateMachineException;
33 import org.onap.policy.apex.model.basicmodel.concepts.AxKey;
34 import org.slf4j.ext.XLogger;
35 import org.slf4j.ext.XLoggerFactory;
38 * The Class JavascriptExecutor is the executor for task logic written in Javascript.
40 * @author Liam Fallon (liam.fallon@ericsson.com)
42 public class JavascriptExecutor implements Runnable {
43 private static final XLogger LOGGER = XLoggerFactory.getXLogger(JavascriptExecutor.class);
45 public static final int DEFAULT_OPTIMIZATION_LEVEL = 9;
47 // Recurring string constants
48 private static final String WITH_MESSAGE = " with message: ";
50 // The key of the subject that wants to execute Javascript code
51 final AxKey subjectKey;
53 private String javascriptCode;
54 private Context javascriptContext;
55 private Script script;
57 private final BlockingQueue<Object> executionQueue = new LinkedBlockingQueue<>();
58 private final BlockingQueue<Boolean> resultQueue = new LinkedBlockingQueue<>();
60 private final Thread executorThread;
61 private CountDownLatch intializationLatch = new CountDownLatch(1);
62 private CountDownLatch shutdownLatch = new CountDownLatch(1);
63 private AtomicReference<StateMachineException> executorException = new AtomicReference<>(null);
66 * Initializes the Javascript executor.
68 * @param subjectKey the key of the subject that is requesting Javascript execution
70 public JavascriptExecutor(final AxKey subjectKey) {
71 this.subjectKey = subjectKey;
73 executorThread = new Thread(this);
74 executorThread.setName(this.getClass().getSimpleName() + ":" + subjectKey.getId());
78 * Prepares the executor for processing and compiles the Javascript code.
80 * @param javascriptCode the Javascript code to execute
81 * @throws StateMachineException thrown when instantiation of the executor fails
83 public void init(final String javascriptCode) throws StateMachineException {
84 LOGGER.debug("JavascriptExecutor {} starting ... ", subjectKey.getId());
86 if (executorThread.isAlive()) {
87 throw new StateMachineException(
88 "initiation failed, executor " + subjectKey.getId() + " is already running");
91 if (StringUtils.isEmpty(javascriptCode)) {
92 throw new StateMachineException("no logic specified for " + subjectKey.getId());
95 this.javascriptCode = javascriptCode;
98 executorThread.start();
99 } catch (Exception e) {
100 throw new StateMachineException("initiation failed, executor " + subjectKey.getId() + " failed to start",
105 intializationLatch.await();
106 } catch (InterruptedException e) {
107 LOGGER.debug("JavascriptExecutor {} interrupted on execution thread startup", subjectKey.getId(), e);
108 Thread.currentThread().interrupt();
111 if (executorException.get() != null) {
112 clearAndThrowExecutorException();
115 LOGGER.debug("JavascriptExecutor {} started ... ", subjectKey.getId());
119 * Execute a Javascript script.
121 * @param executionContext the execution context to use for script execution
122 * @return true if execution was successful, false otherwise
123 * @throws StateMachineException on execution errors
125 public boolean execute(final Object executionContext) throws StateMachineException {
126 if (!executorThread.isAlive()) {
127 throw new StateMachineException("execution failed, executor " + subjectKey.getId() + " is not running");
130 executionQueue.add(executionContext);
132 boolean result = false;
135 result = resultQueue.take();
136 } catch (final InterruptedException e) {
137 LOGGER.debug("JavascriptExecutor {} interrupted on execution result wait", subjectKey.getId(), e);
138 Thread.currentThread().interrupt();
141 if (executorException.get() != null) {
142 clearAndThrowExecutorException();
149 * Cleans up the executor after processing.
151 * @throws StateMachineException thrown when cleanup of the executor fails
153 public void cleanUp() throws StateMachineException {
154 if (!executorThread.isAlive()) {
155 throw new StateMachineException("cleanup failed, executor " + subjectKey.getId() + " is not running");
158 executorThread.interrupt();
161 shutdownLatch.await();
162 } catch (InterruptedException e) {
163 LOGGER.debug("JavascriptExecutor {} interrupted on execution clkeanup wait", subjectKey.getId(), e);
164 Thread.currentThread().interrupt();
167 if (executorException.get() != null) {
168 clearAndThrowExecutorException();
174 LOGGER.debug("JavascriptExecutor {} initializing ... ", subjectKey.getId());
178 } catch (StateMachineException sme) {
179 LOGGER.warn("JavascriptExecutor {} initialization failed", sme);
180 executorException.set(sme);
181 intializationLatch.countDown();
185 intializationLatch.countDown();
187 LOGGER.debug("JavascriptExecutor {} executing ... ", subjectKey.getId());
189 // Take jobs from the execution queue of the worker and execute them
190 while (!executorThread.isInterrupted()) {
192 Object contextObject = executionQueue.take();
193 if (contextObject == null) {
197 resultQueue.add(executeScript(contextObject));
198 } catch (final InterruptedException e) {
199 LOGGER.debug("execution was interruped for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e);
200 resultQueue.add(false);
201 Thread.currentThread().interrupt();
202 } catch (StateMachineException sme) {
203 executorException.set(sme);
204 resultQueue.add(false);
210 } catch (final Exception e) {
211 executorException.set(new StateMachineException(
212 "executor close failed to close for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e));
215 shutdownLatch.countDown();
217 LOGGER.debug("JavascriptExecutor {} completed processing", subjectKey.getId());
220 private void initExecutor() throws StateMachineException {
222 // Create a Javascript context for this thread
223 javascriptContext = Context.enter();
225 // Set up the default values of the context
226 javascriptContext.setOptimizationLevel(DEFAULT_OPTIMIZATION_LEVEL);
227 javascriptContext.setLanguageVersion(Context.VERSION_1_8);
229 script = javascriptContext.compileString(javascriptCode, subjectKey.getId(), 1, null);
230 } catch (Exception e) {
232 throw new StateMachineException(
233 "logic failed to compile for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e);
237 private boolean executeScript(final Object executionContext) throws StateMachineException {
238 Object returnObject = null;
241 // Pass the subject context to the Javascript engine
242 Scriptable javascriptScope = javascriptContext.initStandardObjects();
243 javascriptScope.put("executor", javascriptScope, executionContext);
246 returnObject = script.exec(javascriptContext, javascriptScope);
247 } catch (final Exception e) {
248 throw new StateMachineException(
249 "logic failed to run for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e);
252 if (!(returnObject instanceof Boolean)) {
253 throw new StateMachineException(
254 "execute: logic for " + subjectKey.getId() + " returned a non-boolean value " + returnObject);
257 return (boolean) returnObject;
260 private void clearAndThrowExecutorException() throws StateMachineException {
261 StateMachineException exceptionToThrow = executorException.getAndSet(null);
262 if (exceptionToThrow != null) {
263 throw exceptionToThrow;
267 protected Thread getExecutorThread() {
268 return executorThread;