Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qlexpress #2978

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b126872
[support qlexpress]
taokan Oct 7, 2024
2b88739
Merge branch 'master' of https://github.com/taokan/arthas
taokan Oct 7, 2024
30fe689
【支持qlexpress表达式】
taokan Oct 8, 2024
efebecd
【支持qlexpress表达式】
taokan Oct 8, 2024
b1a0379
【支持qlexpress表达式】
taokan Oct 8, 2024
a878dc6
【支持qlexpress表达式】
taokan Oct 27, 2024
c780218
【支持qlexpress表达式】
taokan Nov 3, 2024
a54ac65
【支持qlexpress表达式】
taokan Nov 3, 2024
cbdf590
【支持qlexpress表达式】
taokan Nov 3, 2024
ec0f951
【支持qlexpress表达式】
taokan Nov 10, 2024
2dab795
【支持qlexpress表达式】
taokan Nov 10, 2024
44fde5c
【支持qlexpress表达式】
taokan Nov 10, 2024
aebe302
【支持qlexpress表达式】
taokan Nov 10, 2024
7d73a17
【支持qlexpress表达式】
taokan Nov 10, 2024
49d9397
【支持qlexpress表达式】
taokan Dec 1, 2024
87b71d0
【支持qlexpress表达式】
taokan Dec 22, 2024
35899a8
【支持qlexpress表达式】
taokan Dec 22, 2024
5a77e93
【支持qlexpress表达式】yarn revert
taokan Dec 22, 2024
adb0257
【支持qlexpress表达式】pom版本升级
taokan Jan 4, 2025
4f3a518
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
564c11a
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
23c3df1
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
74a84f8
【支持qlexpress表达式】冲突
taokan Jan 4, 2025
92cff37
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
63c648c
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
dac03f5
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
99f4850
【支持qlexpress表达式】冲突
taokan Jan 12, 2025
0de1a3f
Merge branch 'qlexpress' into qlexpress20240112
taokan Jan 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -263,6 +267,10 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
</dependencies>

</project>
20 changes: 20 additions & 0 deletions core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
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.
* @author hengyunabc 2018-10-08
*/
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
* @param object
* @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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}


}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Loading
Loading