|
@@ -0,0 +1,182 @@
|
|
|
|
+package org.jetlinks.community.script.jsr223;
|
|
|
|
+
|
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
|
+import lombok.SneakyThrows;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.jetlinks.community.script.AbstractScriptFactory;
|
|
|
|
+import org.jetlinks.community.script.CompiledScript;
|
|
|
|
+import org.jetlinks.community.script.Script;
|
|
|
|
+import org.jetlinks.community.script.context.ExecutionContext;
|
|
|
|
+import org.jetlinks.reactor.ql.utils.CastUtils;
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
+
|
|
|
|
+import javax.script.Compilable;
|
|
|
|
+import javax.script.Invocable;
|
|
|
|
+import javax.script.ScriptContext;
|
|
|
|
+import javax.script.ScriptEngine;
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
+import java.lang.reflect.Proxy;
|
|
|
|
+import java.util.Collections;
|
|
|
|
+import java.util.HashSet;
|
|
|
|
+import java.util.Set;
|
|
|
|
+
|
|
|
|
+@Slf4j
|
|
|
|
+public abstract class Jsr223ScriptFactory extends AbstractScriptFactory {
|
|
|
|
+
|
|
|
|
+ private final ScriptEngine engine;
|
|
|
|
+
|
|
|
|
+ public Jsr223ScriptFactory() {
|
|
|
|
+ this.engine = createEngine();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ protected abstract ScriptEngine createEngine();
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public final CompiledScript compile(Script script) {
|
|
|
|
+ return compile(script, true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private CompiledScript compile(Script script, boolean convert) {
|
|
|
|
+
|
|
|
|
+ ExecutionContext ctx = ExecutionContext.create();
|
|
|
|
+
|
|
|
|
+ ctx.setAttribute("console", new Jsr223ScriptFactory.Console(
|
|
|
|
+ LoggerFactory.getLogger("org.jetlinks.community.script." + script.getName())),
|
|
|
|
+ ScriptContext.ENGINE_SCOPE);
|
|
|
|
+
|
|
|
|
+ ctx.setAttribute("engine", null, ScriptContext.ENGINE_SCOPE);
|
|
|
|
+
|
|
|
|
+ javax.script.CompiledScript compiledScript = compile0(script);
|
|
|
|
+
|
|
|
|
+ return (context) -> Jsr223ScriptFactory.this
|
|
|
|
+ .eval(compiledScript,
|
|
|
|
+ script,
|
|
|
|
+ ExecutionContext.compose(ctx, context),
|
|
|
|
+ convert);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ private Object eval(javax.script.CompiledScript script,
|
|
|
|
+ Script source,
|
|
|
|
+ ExecutionContext context,
|
|
|
|
+ boolean convert) {
|
|
|
|
+ Object res = script.eval(acceptScriptContext(source, context));
|
|
|
|
+
|
|
|
|
+ return convert ? convertToJavaType(res) : res;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ protected ExecutionContext acceptScriptContext(Script script, ExecutionContext context) {
|
|
|
|
+ return context;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @AllArgsConstructor
|
|
|
|
+ public static class Console {
|
|
|
|
+ private final Logger logger;
|
|
|
|
+
|
|
|
|
+ public void trace(String text, Object... args) {
|
|
|
|
+ logger.trace(text, args);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void warn(String text, Object... args) {
|
|
|
|
+ logger.warn(text, args);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void log(String text, Object... args) {
|
|
|
|
+ logger.debug(text, args);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void error(String text, Object... args) {
|
|
|
|
+ logger.error(text, args);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ @SuppressWarnings("all")
|
|
|
|
+ public final <T> T bind(Script script, Class<T> interfaceType) {
|
|
|
|
+ String returns = createFunctionMapping(interfaceType.getDeclaredMethods());
|
|
|
|
+ String content = script.getContent() + "\n return " + returns + ";";
|
|
|
|
+
|
|
|
|
+ CompiledScript compiledScript = compile(script.content(content), false);
|
|
|
|
+ Object source = compiledScript.call(Collections.emptyMap());
|
|
|
|
+ Set<Method> ignoreMethods = new HashSet<>();
|
|
|
|
+
|
|
|
|
+ return (T) Proxy.newProxyInstance(
|
|
|
|
+ interfaceType.getClassLoader(),
|
|
|
|
+ new Class[]{interfaceType},
|
|
|
|
+ (proxy, method, args) -> {
|
|
|
|
+ //方法已经被忽略执行
|
|
|
|
+ if (ignoreMethods.contains(method)) {
|
|
|
|
+ return convertValue(method, null);
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ return this.convertValue(method,
|
|
|
|
+ ((Invocable) engine).invokeMethod(source, method.getName(), args));
|
|
|
|
+ } catch (Throwable e) {
|
|
|
|
+ if (e instanceof NoSuchMethodException) {
|
|
|
|
+ log.info("method [{}] undefined in script", method, e);
|
|
|
|
+ //脚本未定义方法
|
|
|
|
+ ignoreMethods.add(method);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return convertValue(method, null);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected boolean valueIsUndefined(Object value) {
|
|
|
|
+ return value == null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public Object convertToJavaType(Object value) {
|
|
|
|
+ return value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Object convertValue(Method method, Object value) {
|
|
|
|
+ if (valueIsUndefined(value)) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ value = convertToJavaType(value);
|
|
|
|
+
|
|
|
|
+ Class<?> returnType = method.getReturnType();
|
|
|
|
+ if (returnType == void.class) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ if (returnType == int.class) {
|
|
|
|
+ return CastUtils.castNumber(value).intValue();
|
|
|
|
+ }
|
|
|
|
+ if (returnType == float.class) {
|
|
|
|
+ return CastUtils.castNumber(value).floatValue();
|
|
|
|
+ }
|
|
|
|
+ if (returnType == double.class) {
|
|
|
|
+ return CastUtils.castNumber(value).doubleValue();
|
|
|
|
+ }
|
|
|
|
+ if (returnType == long.class) {
|
|
|
|
+ return CastUtils.castNumber(value).longValue();
|
|
|
|
+ }
|
|
|
|
+ if (returnType == byte.class) {
|
|
|
|
+ return CastUtils.castNumber(value).byteValue();
|
|
|
|
+ }
|
|
|
|
+ if (returnType == short.class) {
|
|
|
|
+ return CastUtils.castNumber(value).shortValue();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return value;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected abstract String createFunctionMapping(Method[] methods);
|
|
|
|
+
|
|
|
|
+ @SneakyThrows
|
|
|
|
+ private javax.script.CompiledScript compile0(Script script) {
|
|
|
|
+ String rewriteScript = prepare(script);
|
|
|
|
+ log.debug("compile script :\n{}", rewriteScript);
|
|
|
|
+ return ((Compilable) engine).compile(rewriteScript);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected String prepare(Script script) {
|
|
|
|
+ return script.getContent();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|