Skip to content

Commit

Permalink
Merge branch 'qlexpress' into qlexpress20240112
Browse files Browse the repository at this point in the history
# Conflicts:
#	core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
#	core/src/test/java/com/taobao/arthas/core/command/express/OgnlTest.java
  • Loading branch information
taokan committed Jan 12, 2025
2 parents b266e1c + 99f4850 commit 0de1a3f
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 14 deletions.
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

0 comments on commit 0de1a3f

Please sign in to comment.