feat: 添加 ScriptComponentFactory 以允许通过该接口实例动态生成脚本组件.

通过 ScriptComponentFactory 接口, 可以根据脚本信息动态得构造一些特定于脚本的组件, 例如日志.
本次提交同时调整了 Groovy 脚本的初始化过程, 让 ScriptInfo 的初始化能够更先于脚本 Trigger 的运行.
This commit is contained in:
LamGC 2021-09-02 14:23:37 +08:00
parent c2aa02cae3
commit 2f225d27fe
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
3 changed files with 82 additions and 10 deletions

View File

@ -0,0 +1,22 @@
package net.lamgc.oracle.sentry.script;
/**
* 脚本组件工厂.
* @author LamGC
*/
public interface ScriptComponentFactory<T> {
/**
* 创建并获取实例.
* @param info 脚本信息.
* @return 返回对象.
*/
T getInstance(ScriptInfo info);
/**
* 对象属性名.
* @return 返回建议的对象属性名, {@link ScriptLoader} 并不一定遵守.
*/
String getPropertyName();
}

View File

@ -2,12 +2,15 @@ package net.lamgc.oracle.sentry.script.groovy;
import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import net.lamgc.oracle.sentry.ComputeInstanceManager;
import net.lamgc.oracle.sentry.script.Script;
import net.lamgc.oracle.sentry.script.ScriptInfo;
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
import net.lamgc.oracle.sentry.script.groovy.trigger.GroovyTrigger;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Groovy DSL 脚本的父类.
* @author LamGC
@ -16,11 +19,14 @@ import org.codehaus.groovy.runtime.DefaultGroovyMethods;
public class GroovyDslDelegate implements Script {
private final GroovyScriptInfo scriptInfo = new GroovyScriptInfo();
private final GroovyScriptLoader scriptLoader;
/**
* 构建一个 DSL Delegate, 并传入可操作对象.
* @param scriptLoader 该脚本所属的加载器.
*/
public GroovyDslDelegate() {
public GroovyDslDelegate(GroovyScriptLoader scriptLoader) {
this.scriptLoader = scriptLoader;
}
/**
@ -29,7 +35,11 @@ public class GroovyDslDelegate implements Script {
* @param closure 待执行闭包.
*/
private void trigger(String triggerName, Closure<?> closure){
DefaultGroovyMethods.with(GroovyTriggerProvider.INSTANCE.getTriggerByName(triggerName), closure);
if (!scriptLoader.isInitialed(this)) {
return;
}
GroovyTrigger trigger = GroovyTriggerProvider.INSTANCE.getTriggerByName(triggerName);
DefaultGroovyMethods.with(trigger, closure);
}
/**

View File

@ -1,5 +1,6 @@
package net.lamgc.oracle.sentry.script.groovy;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
@ -8,6 +9,7 @@ import net.lamgc.oracle.sentry.script.Script;
import net.lamgc.oracle.sentry.script.ScriptComponents;
import net.lamgc.oracle.sentry.script.ScriptInfo;
import net.lamgc.oracle.sentry.script.ScriptLoader;
import net.lamgc.oracle.sentry.script.ScriptComponentFactory;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,7 +19,9 @@ import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@ -30,8 +34,8 @@ public class GroovyScriptLoader implements ScriptLoader {
private final static Logger log = LoggerFactory.getLogger(GroovyScriptLoader.class);
private final GroovyClassLoader scriptClassLoader;
private final Map<Script, ScriptInfo> scriptInfoMap = new ConcurrentHashMap<>();
private final Set<Script> initialedScript = new HashSet<>();
/**
* 构造一个新的脚本加载器.
@ -58,11 +62,17 @@ public class GroovyScriptLoader implements ScriptLoader {
Constructor<? extends DelegatingScript> constructor =
scriptClass.asSubclass(DelegatingScript.class).getConstructor();
DelegatingScript newScriptObject = constructor.newInstance();
GroovyDslDelegate dslDelegate = new GroovyDslDelegate();
GroovyDslDelegate dslDelegate = new GroovyDslDelegate(this);
newScriptObject.setDelegate(dslDelegate);
newScriptObject.setBinding(createBinding(context));
newScriptObject.run();
scriptInfoMap.put(dslDelegate, dslDelegate.getScriptInfo());
ScriptInfo scriptInfo = dslDelegate.getScriptInfo();
if (!checkScriptInfo(scriptInfo)) {
return null;
}
initialedScript.add(dslDelegate);
newScriptObject.setBinding(createBinding(context, scriptInfo));
newScriptObject.run();
scriptInfoMap.put(dslDelegate, scriptInfo);
return dslDelegate;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
log.error("加载脚本时发生异常.(ScriptPath: {})\n{}", scriptFile.getAbsolutePath(), Throwables.getStackTraceAsString(e));
@ -75,18 +85,48 @@ public class GroovyScriptLoader implements ScriptLoader {
return scriptInfoMap.get(script);
}
private static Binding createBinding(ScriptComponents components) {
/**
* 检查脚本是否已经初始化完毕.
* @param script 脚本对象.
* @return 如果已初始化完毕, 返回 {@code true}.
*/
public boolean isInitialed(GroovyDslDelegate script) {
return initialedScript.contains(script);
}
private static Binding createBinding(ScriptComponents components, ScriptInfo info) {
Binding binding = new Binding();
for (Field field : components.getClass().getDeclaredFields()) {
try {
String name = field.getName();
field.setAccessible(true);
Object o = field.get(components);
binding.setProperty(name, o);
if (o instanceof ScriptComponentFactory factory) {
binding.setProperty(factory.getPropertyName(), factory.getInstance(info));
} else {
binding.setProperty(name, o);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return binding;
}
private static boolean checkScriptInfo(ScriptInfo info) {
if (Strings.isNullOrEmpty(info.getGroup())) {
log.warn("脚本信息缺少 {}, 跳过加载.", "Group");
return false;
}
if (Strings.isNullOrEmpty(info.getName())) {
log.warn("脚本信息缺少 {}, 跳过加载.", "Name");
return false;
}
if (Strings.isNullOrEmpty(info.getVersion())) {
log.warn("脚本信息缺少 {}, 跳过加载.", "Version");
return false;
}
return true;
}
}