001/** 002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.gnu.org/licenses/lgpl-3.0.txt 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package dev.tinyflow.core.code.impl; 017 018import dev.tinyflow.core.chain.Chain; 019import dev.tinyflow.core.code.CodeRuntimeEngine; 020import dev.tinyflow.core.node.CodeNode; 021import dev.tinyflow.core.util.graalvm.JsInteropUtils; 022import org.graalvm.polyglot.Context; 023import org.graalvm.polyglot.HostAccess; 024import org.graalvm.polyglot.Value; 025 026import java.util.Map; 027 028public class JavascriptRuntimeEngine implements CodeRuntimeEngine { 029 030 // 使用 Context.Builder 构建上下文,线程安全 031 private static final Context.Builder CONTEXT_BUILDER = Context.newBuilder("js") 032 .option("engine.WarnInterpreterOnly", "false") 033 .allowHostAccess(HostAccess.ALL) // 允许访问 Java 对象的方法和字段 034 .allowHostClassLookup(className -> false) // 禁止动态加载任意 Java 类 035 .option("js.ecmascript-version", "2021"); // 使用较新的 ECMAScript 版本 036 037 038 @Override 039 public Map<String, Object> execute(String code, CodeNode node, Chain chain) { 040 try (Context context = CONTEXT_BUILDER.build()) { 041 Value bindings = context.getBindings("js"); 042 043 Map<String, Object> all = chain.getState().getMemory(); 044 all.forEach((key, value) -> { 045 if (!key.contains(".")) { 046 bindings.putMember(key, JsInteropUtils.wrapJavaValueForJS(context, value)); 047 } 048 }); 049 050 // 注入参数 051 Map<String, Object> parameterValues = chain.getState().resolveParameters(node); 052 if (parameterValues != null) { 053 for (Map.Entry<String, Object> entry : parameterValues.entrySet()) { 054 bindings.putMember(entry.getKey(), JsInteropUtils.wrapJavaValueForJS(context, entry.getValue())); 055 } 056 } 057 058 bindings.putMember("_chain", chain); 059 bindings.putMember("_state", chain.getNodeState(node.getId())); 060 061 062 // 在 JS 中创建 _result 对象 063 context.eval("js", "var _result = {};"); 064 065 // 注入 _chain 和 _context 066 bindings.putMember("_chain", chain); 067 bindings.putMember("_state", chain.getNodeState(node.getId())); 068 069 // 执行用户脚本 070 context.eval("js", code); 071 072 Value resultValue = bindings.getMember("_result"); 073 074 return GraalvmToFastJSONUtils.toJSONObject(resultValue); 075 076 } catch (Exception e) { 077 throw new RuntimeException("Polyglot JS 脚本执行失败: " + e.getMessage(), e); 078 } 079 } 080 081}