]> git.basschouten.com Git - openhab-addons.git/blob
911575a92e9a0d02016b128f013f2b0fa0e8fa88
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.transform.javascript.internal;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.Reader;
20 import java.nio.charset.StandardCharsets;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.time.Duration;
24 import java.util.Base64;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27
28 import javax.script.Compilable;
29 import javax.script.CompiledScript;
30 import javax.script.ScriptEngine;
31 import javax.script.ScriptEngineManager;
32 import javax.script.ScriptException;
33
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.openhab.core.cache.ExpiringCacheMap;
36 import org.openhab.core.transform.TransformationException;
37 import org.osgi.service.component.annotations.Component;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Simple cache for compiled JavaScript files.
43  *
44  * @author Thomas Kordelle - Initial contribution
45  * @author Thomas Kordelle - pre compiled scripts
46  */
47 @NonNullByDefault
48 @Component(service = JavaScriptEngineManager.class)
49 public class JavaScriptEngineManager {
50
51     private final Logger logger = LoggerFactory.getLogger(JavaScriptEngineManager.class);
52     private final ScriptEngineManager manager = new ScriptEngineManager();
53     /* keep memory foot print low. max 2 concurrent threads are estimated */
54     private final Map<String, CompiledScript> compiledScriptMap = new ConcurrentHashMap<>(4, 0.5f, 2);
55     private final ExpiringCacheMap<String, CompiledScript> cacheForInlineScripts = new ExpiringCacheMap<>(
56             Duration.ofDays(1));
57
58     /**
59      * Get a pre compiled script {@link CompiledScript} from cache. If it is not in the cache, then load it from
60      * storage and put a pre compiled version into the cache.
61      *
62      * @param filename name of the JavaScript file to load
63      * @return a pre compiled script {@link CompiledScript}
64      * @throws TransformationException if compile of JavaScript failed
65      */
66     protected CompiledScript getCompiledScriptByFilename(final String filename) throws TransformationException {
67         synchronized (compiledScriptMap) {
68             CompiledScript compiledScript = compiledScriptMap.get(filename);
69             if (compiledScript != null) {
70                 logger.debug("Loading JavaScript {} from cache.", filename);
71                 return compiledScript;
72             } else {
73                 final String path = TransformationScriptWatcher.TRANSFORM_FOLDER + File.separator + filename;
74                 logger.debug("Loading script {} from storage ", path);
75                 try (final Reader reader = new InputStreamReader(new FileInputStream(path))) {
76                     final ScriptEngine engine = manager.getEngineByName("javascript");
77                     final CompiledScript cScript = ((Compilable) engine).compile(reader);
78                     logger.debug("Putting compiled JavaScript {} to cache.", cScript);
79                     compiledScriptMap.put(filename, cScript);
80                     return cScript;
81                 } catch (IOException | ScriptException e) {
82                     throw new TransformationException("An error occurred while loading JavaScript. " + e.getMessage(),
83                             e);
84                 }
85             }
86         }
87     }
88
89     /**
90      * Get a pre compiled script {@link CompiledScript} from cache. If it is not in the cache, then compile
91      * it and put a pre compiled version into the cache.
92      *
93      * @param script JavaScript which should be returned as a pre compiled
94      * @return a pre compiled script {@link CompiledScript}
95      * @throws TransformationException if compile of JavaScript failed
96      */
97     protected CompiledScript getCompiledScriptByInlineScript(final String script) throws TransformationException {
98         synchronized (cacheForInlineScripts) {
99             try {
100                 final String hash = calcHash(script);
101                 final CompiledScript compiledScript = cacheForInlineScripts.get(hash);
102                 if (compiledScript != null) {
103                     logger.debug("Loading JavaScript from cache.");
104                     return compiledScript;
105                 } else {
106                     logger.debug("Compiling script {}", script);
107                     final ScriptEngine engine = manager.getEngineByName("javascript");
108                     final CompiledScript cScript = ((Compilable) engine).compile(script);
109                     cacheForInlineScripts.put(hash, () -> cScript);
110                     return cScript;
111                 }
112             } catch (ScriptException | NoSuchAlgorithmException e) {
113                 throw new TransformationException("An error occurred while compiling JavaScript. " + e.getMessage(), e);
114             }
115         }
116     }
117
118     /**
119      * remove a pre compiled script from cache.
120      *
121      * @param fileName name of the script file to remove
122      */
123     protected void removeFromCache(String fileName) {
124         logger.debug("Removing JavaScript {} from cache.", fileName);
125         compiledScriptMap.remove(fileName);
126     }
127
128     private String calcHash(final String script) throws NoSuchAlgorithmException {
129         MessageDigest digest = MessageDigest.getInstance("SHA-256");
130         byte[] hash = digest.digest(script.getBytes(StandardCharsets.UTF_8));
131         return Base64.getEncoder().encodeToString(hash);
132     }
133 }