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.java
pull/2978/head
taokankan.tk 4 weeks ago
commit 0de1a3fcb5

@ -211,6 +211,10 @@
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress4</artifactId>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
@ -263,6 +267,10 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
</dependencies>
</project>

@ -142,6 +142,26 @@ public class GlobalOptions {
description = STRICT_MESSAGE
)
public static volatile boolean strict = true;
/**
* 使ognl/qlexpress
*/
@Option(level = 1,
name = "express-type",
summary = "Option to use ognl/qlexpress",
description = "Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress"
)
public static volatile String ExpressType = "ognl";
/**
* qlexpress使
*/
@Option(level = 1,
name = "qlexpress-config",
summary = "config init when use qlexpress, with json-simple, for example: {\"precise\": true }",
description = ""
)
public static volatile String QLExpressConfig = "";
public static void updateOnglStrict(boolean strict) {
try {

@ -1,5 +1,9 @@
package com.taobao.arthas.core.command.express;
import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
/**
* ExpressFactory
* @author ralf0131 2017-01-04 14:40.
@ -7,12 +11,8 @@ package com.taobao.arthas.core.command.express;
*/
public class ExpressFactory {
private static final ThreadLocal<Express> expressRef = new ThreadLocal<Express>() {
@Override
protected Express initialValue() {
return new OgnlExpress();
}
};
private static final ThreadLocal<Express> expressRef = ThreadLocal.withInitial(() -> new OgnlExpress());
private static final ThreadLocal<Express> expressRefQLExpress = ThreadLocal.withInitial(() -> new QLExpress());
/**
* get ThreadLocal Express Object
@ -20,10 +20,23 @@ public class ExpressFactory {
* @return
*/
public static Express threadLocalExpress(Object object) {
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
return expressRefQLExpress.get().reset().bind(object);
}
return expressRef.get().reset().bind(object);
}
public static Express unpooledExpress(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
}
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
}
public static Express unpooledExpressByOGNL(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}

@ -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;
}
}

@ -31,13 +31,13 @@ import com.taobao.middleware.cli.annotations.Summary;
@Name("ognl")
@Summary("Execute ognl expression.")
@Description(Constants.EXAMPLE
+ " ognl '@java.lang.System@out.println(\"hello \\u4e2d\\u6587\")' \n"
+ " ognl -x 2 '@Singleton@getInstance()' \n"
+ " ognl '@Demo@staticFiled' \n"
+ " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
+ Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
+ " https://commons.apache.org/proper/commons-ognl/language-guide.html")
+ " ognl '@java.lang.System@out.println(\"hello \\u4e2d\\u6587\")' \n"
+ " ognl -x 2 '@Singleton@getInstance()' \n"
+ " ognl '@Demo@staticFiled' \n"
+ " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
+ Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
+ " https://commons.apache.org/proper/commons-ognl/language-guide.html")
public class OgnlCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(OgnlCommand.class);
@ -100,7 +100,7 @@ public class OgnlCommand extends AnnotatedCommand {
classLoader = ClassLoader.getSystemClassLoader();
}
Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
Express unpooledExpress = ExpressFactory.unpooledExpressByOGNL(classLoader);
try {
// https://github.com/alibaba/arthas/issues/2892
Object value = unpooledExpress.bind(new Object()).get(express);

@ -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);
}
}

@ -163,6 +163,11 @@
<artifactId>ognl</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress4</artifactId>
<version>4.0.0-beta.2</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>

Loading…
Cancel
Save