mirror of https://github.com/alibaba/arthas.git
Merge branch 'qlexpress' into qlexpress20240112
# Conflicts: # core/src/main/java/com/taobao/arthas/core/GlobalOptions.java # core/src/test/java/com/taobao/arthas/core/command/express/OgnlTest.javapull/2978/head
commit
0de1a3fcb5
@ -0,0 +1,109 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qlexpress4.ClassSupplier;
|
||||
import com.alibaba.qlexpress4.Express4Runner;
|
||||
import com.alibaba.qlexpress4.InitOptions;
|
||||
import com.alibaba.qlexpress4.QLOptions;
|
||||
import com.alibaba.qlexpress4.runtime.ReflectLoader;
|
||||
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.command.model.QLExpressConfigModel;
|
||||
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/9/17 6:01 PM
|
||||
*/
|
||||
public class QLExpress implements Express {
|
||||
private static final Logger logger = LoggerFactory.getLogger(QLExpress.class);
|
||||
private Express4Runner expressRunner;
|
||||
private QLGlobalContext qlGlobalContext;
|
||||
|
||||
private QLOptions qlOptions;
|
||||
|
||||
private InitOptions initOptions;
|
||||
|
||||
public QLExpress() {
|
||||
this(QLExpressCustomClassResolver.customClassResolver);
|
||||
}
|
||||
|
||||
public QLExpress(ClassSupplier classResolver) {
|
||||
initQLExpress(classResolver);
|
||||
initConfig();
|
||||
initContext();
|
||||
}
|
||||
|
||||
private void initConfig() {
|
||||
try {
|
||||
if (GlobalOptions.QLExpressConfig.length() > 0) {
|
||||
QLOptions.Builder qlOptionsBuilder = QLOptions.builder();
|
||||
QLExpressConfigModel qlExpressConfigModel = JSON.parseObject(GlobalOptions.QLExpressConfig, QLExpressConfigModel.class);
|
||||
qlOptionsBuilder.cache(qlExpressConfigModel.isCache());
|
||||
qlOptionsBuilder.avoidNullPointer(qlExpressConfigModel.isAvoidNullPointer());
|
||||
qlOptionsBuilder.maxArrLength(qlExpressConfigModel.getMaxArrLength());
|
||||
qlOptionsBuilder.polluteUserContext(qlExpressConfigModel.isPolluteUserContext());
|
||||
qlOptionsBuilder.precise(qlExpressConfigModel.isPrecise());
|
||||
qlOptionsBuilder.timeoutMillis(qlExpressConfigModel.getTimeoutMillis());
|
||||
qlOptions = qlOptionsBuilder.build();
|
||||
}else {
|
||||
qlOptions = QLOptions.DEFAULT_OPTIONS;
|
||||
}
|
||||
//4.0设置InitOptions
|
||||
}catch (Throwable t){
|
||||
//异常不设置options
|
||||
logger.error("Error Init Options For QLExpress:", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void initQLExpress(ClassSupplier classResolver) {
|
||||
InitOptions.Builder initOptionsBuilder = InitOptions.builder();
|
||||
initOptionsBuilder.securityStrategy(QLSecurityStrategy.open());
|
||||
initOptionsBuilder.allowPrivateAccess(true);
|
||||
initOptionsBuilder.classSupplier(classResolver);
|
||||
initOptions = initOptionsBuilder.build();
|
||||
expressRunner = QLExpressRunner.getInstance(initOptions);
|
||||
}
|
||||
|
||||
private void initContext() {
|
||||
ReflectLoader reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.getExtensionFunctions(), initOptions.isAllowPrivateAccess());
|
||||
qlGlobalContext = new QLGlobalContext(reflectLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String express) throws ExpressException {
|
||||
try {
|
||||
Object result = expressRunner.execute(express, qlGlobalContext, qlOptions);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during evaluating the expression with QLExpress:", e);
|
||||
throw new ExpressException(express, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean is(String express) throws ExpressException {
|
||||
final Object ret = get(express);
|
||||
return ret instanceof Boolean && (Boolean) ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express bind(Object object) {
|
||||
qlGlobalContext.bindObj(object);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express bind(String name, Object value) {
|
||||
qlGlobalContext.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Express reset() {
|
||||
qlGlobalContext.clear();
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.alibaba.qlexpress4.ClassSupplier;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/12/1 7:07 PM
|
||||
*/
|
||||
public class QLExpressClassLoaderClassResolver implements ClassSupplier {
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public QLExpressClassLoaderClassResolver(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
|
||||
try {
|
||||
Class<?> aClass = null;
|
||||
if (classLoader != null) {
|
||||
aClass = classLoader.loadClass(clsQualifiedName);
|
||||
}else {
|
||||
aClass = Class.forName(clsQualifiedName);
|
||||
}
|
||||
return Optional.of(aClass);
|
||||
} catch (ClassNotFoundException | NoClassDefFoundError e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Class<?> loadCls(String className) {
|
||||
Optional<Class<?>> clsOp = cache.computeIfAbsent(className, this::loadClsInner);
|
||||
return clsOp.orElse(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.alibaba.qlexpress4.ClassSupplier;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/12/1 7:06 PM
|
||||
*/
|
||||
public class QLExpressCustomClassResolver implements ClassSupplier {
|
||||
|
||||
public static final QLExpressCustomClassResolver customClassResolver = new QLExpressCustomClassResolver();
|
||||
|
||||
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private QLExpressCustomClassResolver() {
|
||||
|
||||
}
|
||||
|
||||
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Class<?> aClass = null;
|
||||
if (classLoader != null) {
|
||||
aClass = classLoader.loadClass(clsQualifiedName);
|
||||
} else {
|
||||
aClass = Class.forName(clsQualifiedName);
|
||||
}
|
||||
return Optional.of(aClass);
|
||||
} catch (ClassNotFoundException | NoClassDefFoundError e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Class<?> loadCls(String clsQualifiedName) {
|
||||
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
|
||||
return clsOp.orElse(null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
|
||||
import com.alibaba.qlexpress4.Express4Runner;
|
||||
import com.alibaba.qlexpress4.InitOptions;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/9/22 12:20 PM
|
||||
*/
|
||||
public class QLExpressRunner {
|
||||
private volatile static QLExpressRunner instance = null;
|
||||
private Express4Runner expressRunner;
|
||||
|
||||
private QLExpressRunner(InitOptions initOptions){
|
||||
expressRunner = new Express4Runner(initOptions);
|
||||
}
|
||||
|
||||
//对外提供静态方法获取对象
|
||||
public static Express4Runner getInstance(InitOptions initOptions){
|
||||
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
|
||||
if(instance == null){
|
||||
synchronized (QLExpressRunner.class){
|
||||
//抢到锁之后再次进行判断是否为null
|
||||
if(instance == null){
|
||||
instance = new QLExpressRunner(initOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance.expressRunner;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qlexpress4.exception.PureErrReporter;
|
||||
import com.alibaba.qlexpress4.runtime.ReflectLoader;
|
||||
import com.alibaba.qlexpress4.runtime.Value;
|
||||
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
|
||||
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/9/22 12:39 PM
|
||||
*/
|
||||
public class QLGlobalContext implements ExpressContext {
|
||||
private static final Logger logger = LoggerFactory.getLogger(QLGlobalContext.class);
|
||||
|
||||
private Map<String, Object> context;
|
||||
private Object object;
|
||||
private ReflectLoader reflectLoader;
|
||||
|
||||
public QLGlobalContext(ReflectLoader reflectLoader) {
|
||||
this.context = new ConcurrentHashMap<>();
|
||||
this.reflectLoader = reflectLoader;
|
||||
}
|
||||
|
||||
public void put(String name, Object value){
|
||||
context.put(name, value);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
context.clear();
|
||||
this.context.put("reflectLoader",reflectLoader);
|
||||
}
|
||||
|
||||
public void bindObj(Object object) {
|
||||
this.object = object;
|
||||
context.put("object",object);
|
||||
}
|
||||
@Override
|
||||
public Value get(Map<String, Object> attachments, String variableName) {
|
||||
if ((this.reflectLoader != null) && (this.object != null) && !variableName.startsWith("#")) {
|
||||
return this.reflectLoader.loadField(this.object, variableName, true, PureErrReporter.INSTANCE);
|
||||
}
|
||||
String newVariableName = variableName.replace("#","");
|
||||
return new MapItemValue(this.context, newVariableName);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(Map<String, Object> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
public void setObject(Object object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.taobao.arthas.core.command.model;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/9/22 7:32 AM
|
||||
*/
|
||||
public enum ExpressTypeEnum
|
||||
{
|
||||
OGNL("ognl"),
|
||||
QLEXPRESS("qlexpress");
|
||||
|
||||
private String expressType;
|
||||
|
||||
ExpressTypeEnum(String expressType) {
|
||||
this.expressType = expressType;
|
||||
}
|
||||
|
||||
public String getExpressType() {
|
||||
return expressType;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.taobao.arthas.core.command.model;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2024/9/22 7:51 AM
|
||||
*/
|
||||
public class QLExpressConfigModel {
|
||||
//QL_OPTIONS
|
||||
private boolean precise = false;
|
||||
private boolean polluteUserContext = false;
|
||||
private long timeoutMillis = -1L;
|
||||
|
||||
private boolean cache = false;
|
||||
private boolean avoidNullPointer = false;
|
||||
private int maxArrLength = -1;
|
||||
|
||||
|
||||
//INIT_OPTIONS
|
||||
private boolean allowPrivateAccess = true;
|
||||
private boolean debug;
|
||||
private boolean useCacheClear;
|
||||
|
||||
public boolean isAllowPrivateAccess() {
|
||||
return allowPrivateAccess;
|
||||
}
|
||||
|
||||
public void setAllowPrivateAccess(boolean allowPrivateAccess) {
|
||||
this.allowPrivateAccess = allowPrivateAccess;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
public boolean isUseCacheClear() {
|
||||
return useCacheClear;
|
||||
}
|
||||
|
||||
public void setUseCacheClear(boolean useCacheClear) {
|
||||
this.useCacheClear = useCacheClear;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isPrecise() {
|
||||
return precise;
|
||||
}
|
||||
|
||||
public void setPrecise(boolean precise) {
|
||||
this.precise = precise;
|
||||
}
|
||||
|
||||
public boolean isPolluteUserContext() {
|
||||
return polluteUserContext;
|
||||
}
|
||||
|
||||
public void setPolluteUserContext(boolean polluteUserContext) {
|
||||
this.polluteUserContext = polluteUserContext;
|
||||
}
|
||||
|
||||
public long getTimeoutMillis() {
|
||||
return timeoutMillis;
|
||||
}
|
||||
|
||||
public void setTimeoutMillis(long timeoutMillis) {
|
||||
this.timeoutMillis = timeoutMillis;
|
||||
}
|
||||
|
||||
public boolean isAvoidNullPointer() {
|
||||
return avoidNullPointer;
|
||||
}
|
||||
|
||||
public void setAvoidNullPointer(boolean avoidNullPointer) {
|
||||
this.avoidNullPointer = avoidNullPointer;
|
||||
}
|
||||
|
||||
public int getMaxArrLength() {
|
||||
return maxArrLength;
|
||||
}
|
||||
|
||||
public void setMaxArrLength(int maxArrLength) {
|
||||
this.maxArrLength = maxArrLength;
|
||||
}
|
||||
|
||||
|
||||
public boolean isCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void setCache(boolean cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.taobao.arthas.core.command.express;
|
||||
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.advisor.Advice;
|
||||
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
|
||||
import ognl.OgnlException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* @Author TaoKan
|
||||
* @Date 2025/1/12 5:31 PM
|
||||
*/
|
||||
public class QLExpressTest {
|
||||
|
||||
private Express express;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws ExpressException {
|
||||
FlowContext context = new FlowContext();
|
||||
Object[] params = new Object[4];
|
||||
params[0] = context;
|
||||
Advice advice = Advice.newForAfterReturning(null, getClass(), null, null, params, null);
|
||||
GlobalOptions.ExpressType = ExpressTypeEnum.QLEXPRESS.getExpressType();
|
||||
express = ExpressFactory.unpooledExpress(null).bind(advice).bind("cost", 123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringEquals() throws OgnlException, ExpressException {
|
||||
String conditionExpress = "\"aaa\".equals(params[0].flowAttribute.getBxApp())";
|
||||
boolean result = express.is(conditionExpress);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectEquals() throws OgnlException, ExpressException {
|
||||
String conditionExpress = "params[0].flowAttribute.getBxApp().equals(\"aaa\")";
|
||||
boolean result = express.is(conditionExpress);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualSign() throws OgnlException, ExpressException {
|
||||
String conditionExpress = "\"aaa\" == params[0].flowAttribute.getBxApp()";
|
||||
boolean result = express.is(conditionExpress);
|
||||
assertTrue(result);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue