Skip to content

Commit 0de1a3f

Browse files
committed
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
2 parents b266e1c + 99f4850 commit 0de1a3f

File tree

13 files changed

+525
-14
lines changed

13 files changed

+525
-14
lines changed

core/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@
211211
<groupId>ognl</groupId>
212212
<artifactId>ognl</artifactId>
213213
</dependency>
214+
<dependency>
215+
<groupId>com.alibaba</groupId>
216+
<artifactId>QLExpress4</artifactId>
217+
</dependency>
214218
<dependency>
215219
<groupId>org.junit.vintage</groupId>
216220
<artifactId>junit-vintage-engine</artifactId>
@@ -263,6 +267,10 @@
263267
<scope>provided</scope>
264268
<optional>true</optional>
265269
</dependency>
270+
<dependency>
271+
<groupId>ognl</groupId>
272+
<artifactId>ognl</artifactId>
273+
</dependency>
266274
</dependencies>
267275

268276
</project>

core/src/main/java/com/taobao/arthas/core/GlobalOptions.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,26 @@ public class GlobalOptions {
142142
description = STRICT_MESSAGE
143143
)
144144
public static volatile boolean strict = true;
145+
/**
146+
* 是否切换使用表达式ognl/qlexpress开关
147+
*/
148+
@Option(level = 1,
149+
name = "express-type",
150+
summary = "Option to use ognl/qlexpress",
151+
description = "Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress"
152+
)
153+
public static volatile String ExpressType = "ognl";
154+
155+
156+
/**
157+
* qlexpress使用参数
158+
*/
159+
@Option(level = 1,
160+
name = "qlexpress-config",
161+
summary = "config init when use qlexpress, with json-simple, for example: {\"precise\": true }",
162+
description = ""
163+
)
164+
public static volatile String QLExpressConfig = "";
145165

146166
public static void updateOnglStrict(boolean strict) {
147167
try {

core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
11
package com.taobao.arthas.core.command.express;
22

3+
import com.taobao.arthas.core.GlobalOptions;
4+
import com.taobao.arthas.core.command.model.ExpressTypeEnum;
5+
6+
37
/**
48
* ExpressFactory
59
* @author ralf0131 2017-01-04 14:40.
610
* @author hengyunabc 2018-10-08
711
*/
812
public class ExpressFactory {
913

10-
private static final ThreadLocal<Express> expressRef = new ThreadLocal<Express>() {
11-
@Override
12-
protected Express initialValue() {
13-
return new OgnlExpress();
14-
}
15-
};
14+
private static final ThreadLocal<Express> expressRef = ThreadLocal.withInitial(() -> new OgnlExpress());
15+
private static final ThreadLocal<Express> expressRefQLExpress = ThreadLocal.withInitial(() -> new QLExpress());
1616

1717
/**
1818
* get ThreadLocal Express Object
1919
* @param object
2020
* @return
2121
*/
2222
public static Express threadLocalExpress(Object object) {
23+
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
24+
return expressRefQLExpress.get().reset().bind(object);
25+
}
2326
return expressRef.get().reset().bind(object);
2427
}
2528

2629
public static Express unpooledExpress(ClassLoader classloader) {
30+
if (classloader == null) {
31+
classloader = ClassLoader.getSystemClassLoader();
32+
}
33+
if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
34+
return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
35+
}
36+
return new OgnlExpress(new ClassLoaderClassResolver(classloader));
37+
}
38+
39+
public static Express unpooledExpressByOGNL(ClassLoader classloader) {
2740
if (classloader == null) {
2841
classloader = ClassLoader.getSystemClassLoader();
2942
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.alibaba.fastjson2.JSON;
6+
import com.alibaba.qlexpress4.ClassSupplier;
7+
import com.alibaba.qlexpress4.Express4Runner;
8+
import com.alibaba.qlexpress4.InitOptions;
9+
import com.alibaba.qlexpress4.QLOptions;
10+
import com.alibaba.qlexpress4.runtime.ReflectLoader;
11+
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
12+
import com.taobao.arthas.core.GlobalOptions;
13+
import com.taobao.arthas.core.command.model.QLExpressConfigModel;
14+
15+
16+
/**
17+
* @Author TaoKan
18+
* @Date 2024/9/17 6:01 PM
19+
*/
20+
public class QLExpress implements Express {
21+
private static final Logger logger = LoggerFactory.getLogger(QLExpress.class);
22+
private Express4Runner expressRunner;
23+
private QLGlobalContext qlGlobalContext;
24+
25+
private QLOptions qlOptions;
26+
27+
private InitOptions initOptions;
28+
29+
public QLExpress() {
30+
this(QLExpressCustomClassResolver.customClassResolver);
31+
}
32+
33+
public QLExpress(ClassSupplier classResolver) {
34+
initQLExpress(classResolver);
35+
initConfig();
36+
initContext();
37+
}
38+
39+
private void initConfig() {
40+
try {
41+
if (GlobalOptions.QLExpressConfig.length() > 0) {
42+
QLOptions.Builder qlOptionsBuilder = QLOptions.builder();
43+
QLExpressConfigModel qlExpressConfigModel = JSON.parseObject(GlobalOptions.QLExpressConfig, QLExpressConfigModel.class);
44+
qlOptionsBuilder.cache(qlExpressConfigModel.isCache());
45+
qlOptionsBuilder.avoidNullPointer(qlExpressConfigModel.isAvoidNullPointer());
46+
qlOptionsBuilder.maxArrLength(qlExpressConfigModel.getMaxArrLength());
47+
qlOptionsBuilder.polluteUserContext(qlExpressConfigModel.isPolluteUserContext());
48+
qlOptionsBuilder.precise(qlExpressConfigModel.isPrecise());
49+
qlOptionsBuilder.timeoutMillis(qlExpressConfigModel.getTimeoutMillis());
50+
qlOptions = qlOptionsBuilder.build();
51+
}else {
52+
qlOptions = QLOptions.DEFAULT_OPTIONS;
53+
}
54+
//4.0设置InitOptions
55+
}catch (Throwable t){
56+
//异常不设置options
57+
logger.error("Error Init Options For QLExpress:", t);
58+
}
59+
}
60+
61+
private void initQLExpress(ClassSupplier classResolver) {
62+
InitOptions.Builder initOptionsBuilder = InitOptions.builder();
63+
initOptionsBuilder.securityStrategy(QLSecurityStrategy.open());
64+
initOptionsBuilder.allowPrivateAccess(true);
65+
initOptionsBuilder.classSupplier(classResolver);
66+
initOptions = initOptionsBuilder.build();
67+
expressRunner = QLExpressRunner.getInstance(initOptions);
68+
}
69+
70+
private void initContext() {
71+
ReflectLoader reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.getExtensionFunctions(), initOptions.isAllowPrivateAccess());
72+
qlGlobalContext = new QLGlobalContext(reflectLoader);
73+
}
74+
75+
@Override
76+
public Object get(String express) throws ExpressException {
77+
try {
78+
Object result = expressRunner.execute(express, qlGlobalContext, qlOptions);
79+
return result;
80+
} catch (Exception e) {
81+
logger.error("Error during evaluating the expression with QLExpress:", e);
82+
throw new ExpressException(express, e);
83+
}
84+
}
85+
86+
@Override
87+
public boolean is(String express) throws ExpressException {
88+
final Object ret = get(express);
89+
return ret instanceof Boolean && (Boolean) ret;
90+
}
91+
92+
@Override
93+
public Express bind(Object object) {
94+
qlGlobalContext.bindObj(object);
95+
return this;
96+
}
97+
98+
@Override
99+
public Express bind(String name, Object value) {
100+
qlGlobalContext.put(name, value);
101+
return this;
102+
}
103+
104+
@Override
105+
public Express reset() {
106+
qlGlobalContext.clear();
107+
return this;
108+
}
109+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.qlexpress4.ClassSupplier;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
/**
9+
* @Author TaoKan
10+
* @Date 2024/12/1 7:07 PM
11+
*/
12+
public class QLExpressClassLoaderClassResolver implements ClassSupplier {
13+
14+
private ClassLoader classLoader;
15+
16+
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
17+
18+
public QLExpressClassLoaderClassResolver(ClassLoader classLoader) {
19+
this.classLoader = classLoader;
20+
}
21+
22+
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
23+
try {
24+
Class<?> aClass = null;
25+
if (classLoader != null) {
26+
aClass = classLoader.loadClass(clsQualifiedName);
27+
}else {
28+
aClass = Class.forName(clsQualifiedName);
29+
}
30+
return Optional.of(aClass);
31+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
32+
return Optional.empty();
33+
}
34+
}
35+
@Override
36+
public Class<?> loadCls(String className) {
37+
Optional<Class<?>> clsOp = cache.computeIfAbsent(className, this::loadClsInner);
38+
return clsOp.orElse(null);
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.alibaba.qlexpress4.ClassSupplier;
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
/**
11+
* @Author TaoKan
12+
* @Date 2024/12/1 7:06 PM
13+
*/
14+
public class QLExpressCustomClassResolver implements ClassSupplier {
15+
16+
public static final QLExpressCustomClassResolver customClassResolver = new QLExpressCustomClassResolver();
17+
18+
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
19+
20+
private QLExpressCustomClassResolver() {
21+
22+
}
23+
24+
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
25+
try {
26+
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
27+
Class<?> aClass = null;
28+
if (classLoader != null) {
29+
aClass = classLoader.loadClass(clsQualifiedName);
30+
} else {
31+
aClass = Class.forName(clsQualifiedName);
32+
}
33+
return Optional.of(aClass);
34+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
35+
return Optional.empty();
36+
}
37+
}
38+
@Override
39+
public Class<?> loadCls(String clsQualifiedName) {
40+
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
41+
return clsOp.orElse(null);
42+
}
43+
44+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
4+
import com.alibaba.qlexpress4.Express4Runner;
5+
import com.alibaba.qlexpress4.InitOptions;
6+
7+
/**
8+
* @Author TaoKan
9+
* @Date 2024/9/22 12:20 PM
10+
*/
11+
public class QLExpressRunner {
12+
private volatile static QLExpressRunner instance = null;
13+
private Express4Runner expressRunner;
14+
15+
private QLExpressRunner(InitOptions initOptions){
16+
expressRunner = new Express4Runner(initOptions);
17+
}
18+
19+
//对外提供静态方法获取对象
20+
public static Express4Runner getInstance(InitOptions initOptions){
21+
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
22+
if(instance == null){
23+
synchronized (QLExpressRunner.class){
24+
//抢到锁之后再次进行判断是否为null
25+
if(instance == null){
26+
instance = new QLExpressRunner(initOptions);
27+
}
28+
}
29+
}
30+
return instance.expressRunner;
31+
}
32+
33+
34+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.taobao.arthas.core.command.express;
2+
3+
import com.alibaba.arthas.deps.org.slf4j.Logger;
4+
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
5+
import com.alibaba.fastjson2.JSON;
6+
import com.alibaba.qlexpress4.exception.PureErrReporter;
7+
import com.alibaba.qlexpress4.runtime.ReflectLoader;
8+
import com.alibaba.qlexpress4.runtime.Value;
9+
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
10+
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
11+
12+
import java.util.Map;
13+
import java.util.concurrent.ConcurrentHashMap;
14+
15+
/**
16+
* @Author TaoKan
17+
* @Date 2024/9/22 12:39 PM
18+
*/
19+
public class QLGlobalContext implements ExpressContext {
20+
private static final Logger logger = LoggerFactory.getLogger(QLGlobalContext.class);
21+
22+
private Map<String, Object> context;
23+
private Object object;
24+
private ReflectLoader reflectLoader;
25+
26+
public QLGlobalContext(ReflectLoader reflectLoader) {
27+
this.context = new ConcurrentHashMap<>();
28+
this.reflectLoader = reflectLoader;
29+
}
30+
31+
public void put(String name, Object value){
32+
context.put(name, value);
33+
}
34+
35+
public void clear() {
36+
context.clear();
37+
this.context.put("reflectLoader",reflectLoader);
38+
}
39+
40+
public void bindObj(Object object) {
41+
this.object = object;
42+
context.put("object",object);
43+
}
44+
@Override
45+
public Value get(Map<String, Object> attachments, String variableName) {
46+
if ((this.reflectLoader != null) && (this.object != null) && !variableName.startsWith("#")) {
47+
return this.reflectLoader.loadField(this.object, variableName, true, PureErrReporter.INSTANCE);
48+
}
49+
String newVariableName = variableName.replace("#","");
50+
return new MapItemValue(this.context, newVariableName);
51+
}
52+
53+
54+
public Map<String, Object> getContext() {
55+
return context;
56+
}
57+
58+
public void setContext(Map<String, Object> context) {
59+
this.context = context;
60+
}
61+
62+
public Object getObject() {
63+
return object;
64+
}
65+
66+
public void setObject(Object object) {
67+
this.object = object;
68+
}
69+
70+
}

0 commit comments

Comments
 (0)