ExpressionLanguageJEXLImpl.java
package com.gh.mygreen.xlsmapper.expression;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gh.mygreen.xlsmapper.util.ArgUtils;
import com.gh.mygreen.xlsmapper.util.Utils;
/**
* 式言語「JEXL」の実装。
* <p>利用する際には、JEXL v3.3以上のライブラリが必要です。
* <p>JEXL v3.3から、ELインジェクション対策として、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a>によるEL式中で参照/実行可能なクラスを制限されます。
* <p>独自のCellConverter / FiledProcessosrなどを実装しているが場合は、システムプロパティ {@literal xlsmapper.jexlPermissions} で指定することができます。
* 複数指定する場合はカンマ区切りで指定します。
* </p>
*
* @version 2.3
* @since 1.5
* @author T.TSUCHIE
*
*/
public class ExpressionLanguageJEXLImpl implements ExpressionLanguage {
private static final Logger logger = LoggerFactory.getLogger(ExpressionLanguageJEXLImpl.class);
/**
* システムプロパティ - JEXLをRESTRICTモードで使用するかどうかフラグ。
*/
public static final String PROPERTY_JEXL_RESTRICTED = "xlsmapper.jexlRestricted";
/**
* システムプロパティ - JEXLをRESTRICTモードで使用する場合のパーミッションを指定する。
*/
public static final String PROPERTY_JEXL_PERMISSIONS = "xlsmapper.jexlPermissions";
/**
* 本ライブラリでJEXLからアクセス許可するパッケージ指定のパーミッション。
*/
private static final String[] LIB_PERMISSIONS =
{"com.gh.mygreen.xlsmapper.*"};
/**
* 独自のJEXLのパーミッション。
*/
private static final String[] USER_PERMISSIONS;
static {
String value = System.getProperty(PROPERTY_JEXL_PERMISSIONS);
if(Utils.isNotEmpty(value)) {
USER_PERMISSIONS = Arrays.stream(value.split(","))
.map(String::trim)
.filter(String::isEmpty)
.collect(Collectors.toList())
.toArray(new String[0]);
} else {
USER_PERMISSIONS = new String[] {};
}
}
/**
* JEXLのキャッシュサイズ。
* <p>キャッシュする式の個数。
*/
private static final int CACHE_SIZE = 256;
private final JexlEngine jexlEngine;
/**
* デフォルトのコンストラクタ。
* <p>パーミッションによる制限を実施する。
*/
public ExpressionLanguageJEXLImpl() {
this(Collections.emptyMap(), true);
}
/**
* JEXLの独自のEL関数とパーミッションを指定するコンストラクタ。
* <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。
*
* @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。
* @param restricted JEXLをRESTRICTEDモードでパーミッションによる制限を行うかどうか。
* @param userPermissions JEXLのパーミッション。
* 詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
*/
public ExpressionLanguageJEXLImpl(final Map<String, Object> userFunctions, final boolean restricted, final String... userPermissions) {
// EL式中で使用可能な関数の登録
Map<String, Object> functions = new HashMap<>();
functions.put("f", CustomFunctions.class);
if (Utils.isNotEmpty(userFunctions)) {
functions.putAll(userFunctions);
}
final JexlPermissions permissions;
if(Utils.toBoolean(System.getProperty(PROPERTY_JEXL_RESTRICTED), restricted)) {
/*
* EL式で本ライブラリのクラス/メソッドのアクセスを許可する。
* ・CustomFunctions以外にも、CellConverter / FieldProcessorでも参照するため。
* ・JEXLv3からサーバーサイド・テンプレート・インジェクション、コマンドインジェクション対策のために、
* 許可されたクラスしか参照できなくなったため、本ライブラリをEL式から参照可能に許可する。
*/
String[] concateedUserPermission = Utils.concat(USER_PERMISSIONS, userPermissions);
permissions = JexlPermissions.RESTRICTED
.compose(Utils.concat(LIB_PERMISSIONS, concateedUserPermission));
} else {
// パーミッションによる制限を行わない。
permissions = JexlPermissions.UNRESTRICTED;
}
this.jexlEngine = new JexlBuilder()
.namespaces(functions)
.permissions(permissions)
.silent(true)
.strict(false) // JEXLv2相当の文法にする。
.cache(CACHE_SIZE)
.create();
}
/**
* {@link JexlEngine}を指定するコンストラクタ。
* @param jexlEngine JEXLの処理エンジン。
*/
public ExpressionLanguageJEXLImpl(final JexlEngine jexlEngine) {
this.jexlEngine = jexlEngine;
}
@SuppressWarnings("unchecked")
@Override
public Object evaluate(final String expression, final Map<String, ?> values) {
ArgUtils.notEmpty(expression, "expression");
ArgUtils.notNull(values, "values");
if(logger.isDebugEnabled()) {
logger.debug("Evaluating JEXL expression: {}", expression);
}
try {
JexlExpression expr = jexlEngine.createExpression(expression);
return expr.evaluate(new MapContext((Map<String, Object>) values));
} catch(Exception ex) {
throw new ExpressionEvaluationException(String.format("Evaluating [%s] script with JEXL failed.", expression), ex);
}
}
/**
* {@link JexlEngine}を取得する。
* @return
*/
public JexlEngine getJexlEngine() {
return jexlEngine;
}
}