ExpressionLanguageJEXLImpl.java

  1. package com.gh.mygreen.xlsmapper.expression;

  2. import java.util.Arrays;
  3. import java.util.Collections;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. import java.util.stream.Collectors;

  7. import org.apache.commons.jexl3.JexlBuilder;
  8. import org.apache.commons.jexl3.JexlEngine;
  9. import org.apache.commons.jexl3.JexlExpression;
  10. import org.apache.commons.jexl3.MapContext;
  11. import org.apache.commons.jexl3.introspection.JexlPermissions;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;

  14. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  15. import com.gh.mygreen.xlsmapper.util.Utils;

  16. /**
  17.  * 式言語「JEXL」の実装。
  18.  * <p>利用する際には、JEXL v3.3以上のライブラリが必要です。
  19.  * <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式中で参照/実行可能なクラスを制限されます。
  20.  * <p>独自のCellConverter / FiledProcessosrなどを実装しているが場合は、システムプロパティ {@literal xlsmapper.jexlPermissions} で指定することができます。
  21.  *    複数指定する場合はカンマ区切りで指定します。
  22.  * </p>
  23.  *
  24.  * @version 2.3
  25.  * @since 1.5
  26.  * @author T.TSUCHIE
  27.  *
  28.  */
  29. public class ExpressionLanguageJEXLImpl implements ExpressionLanguage {
  30.    
  31.     private static final Logger logger = LoggerFactory.getLogger(ExpressionLanguageJEXLImpl.class);
  32.    
  33.     /**
  34.      * システムプロパティ - JEXLをRESTRICTモードで使用するかどうかフラグ。
  35.      */
  36.     public static final String PROPERTY_JEXL_RESTRICTED = "xlsmapper.jexlRestricted";
  37.    
  38.     /**
  39.      * システムプロパティ - JEXLをRESTRICTモードで使用する場合のパーミッションを指定する。
  40.      */
  41.     public static final String PROPERTY_JEXL_PERMISSIONS = "xlsmapper.jexlPermissions";
  42.    
  43.     /**
  44.      * 本ライブラリでJEXLからアクセス許可するパッケージ指定のパーミッション。
  45.      */
  46.     private static final String[] LIB_PERMISSIONS =
  47.         {"com.gh.mygreen.xlsmapper.*"};
  48.    
  49.     /**
  50.      * 独自のJEXLのパーミッション。
  51.      */
  52.     private static final String[] USER_PERMISSIONS;
  53.     static {
  54.         String value = System.getProperty(PROPERTY_JEXL_PERMISSIONS);
  55.         if(Utils.isNotEmpty(value)) {
  56.             USER_PERMISSIONS = Arrays.stream(value.split(","))
  57.                     .map(String::trim)
  58.                     .filter(String::isEmpty)
  59.                     .collect(Collectors.toList())
  60.                     .toArray(new String[0]);
  61.            
  62.         } else {
  63.             USER_PERMISSIONS = new String[] {};
  64.         }
  65.     }
  66.    
  67.     /**
  68.      * JEXLのキャッシュサイズ。
  69.      * <p>キャッシュする式の個数。
  70.      */
  71.     private static final int CACHE_SIZE = 256;
  72.    
  73.     private final JexlEngine jexlEngine;
  74.    
  75.     /**
  76.      * デフォルトのコンストラクタ。
  77.      * <p>パーミッションによる制限を実施する。
  78.      */
  79.     public ExpressionLanguageJEXLImpl() {
  80.         this(Collections.emptyMap(), true);
  81.        
  82.     }
  83.    
  84.     /**
  85.      * JEXLの独自のEL関数とパーミッションを指定するコンストラクタ。
  86.      * <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。
  87.      *
  88.      * @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。
  89.      * @param restricted JEXLをRESTRICTEDモードでパーミッションによる制限を行うかどうか。
  90.      * @param userPermissions JEXLのパーミッション。
  91.      *        詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
  92.      */
  93.     public ExpressionLanguageJEXLImpl(final Map<String, Object> userFunctions, final boolean restricted, final String... userPermissions) {
  94.        
  95.         // EL式中で使用可能な関数の登録
  96.         Map<String, Object> functions = new HashMap<>();
  97.         functions.put("f", CustomFunctions.class);
  98.        
  99.         if (Utils.isNotEmpty(userFunctions)) {
  100.             functions.putAll(userFunctions);
  101.         }

  102.         final JexlPermissions permissions;
  103.         if(Utils.toBoolean(System.getProperty(PROPERTY_JEXL_RESTRICTED), restricted)) {
  104.             /*
  105.              * EL式で本ライブラリのクラス/メソッドのアクセスを許可する。
  106.              * ・CustomFunctions以外にも、CellConverter / FieldProcessorでも参照するため。
  107.              * ・JEXLv3からサーバーサイド・テンプレート・インジェクション、コマンドインジェクション対策のために、
  108.              *   許可されたクラスしか参照できなくなったため、本ライブラリをEL式から参照可能に許可する。
  109.              */
  110.             String[] concateedUserPermission = Utils.concat(USER_PERMISSIONS, userPermissions);
  111.             permissions = JexlPermissions.RESTRICTED
  112.                     .compose(Utils.concat(LIB_PERMISSIONS, concateedUserPermission));
  113.         } else {
  114.             // パーミッションによる制限を行わない。
  115.             permissions = JexlPermissions.UNRESTRICTED;
  116.         }

  117.         this.jexlEngine = new JexlBuilder()
  118.                 .namespaces(functions)
  119.                 .permissions(permissions)
  120.                 .silent(true)
  121.                 .strict(false)  // JEXLv2相当の文法にする。
  122.                 .cache(CACHE_SIZE)
  123.                 .create();
  124.        
  125.     }
  126.    
  127.    
  128.     /**
  129.      * {@link JexlEngine}を指定するコンストラクタ。
  130.      * @param jexlEngine JEXLの処理エンジン。
  131.      */
  132.     public ExpressionLanguageJEXLImpl(final JexlEngine jexlEngine) {
  133.         this.jexlEngine = jexlEngine;
  134.     }
  135.    
  136.     @SuppressWarnings("unchecked")
  137.     @Override
  138.     public Object evaluate(final String expression, final Map<String, ?> values) {
  139.        
  140.         ArgUtils.notEmpty(expression, "expression");
  141.         ArgUtils.notNull(values, "values");
  142.        
  143.         if(logger.isDebugEnabled()) {
  144.             logger.debug("Evaluating JEXL expression: {}", expression);
  145.         }
  146.        
  147.         try {
  148.             JexlExpression expr = jexlEngine.createExpression(expression);
  149.             return expr.evaluate(new MapContext((Map<String, Object>) values));
  150.            
  151.         } catch(Exception ex) {
  152.             throw new ExpressionEvaluationException(String.format("Evaluating [%s] script with JEXL failed.", expression), ex);
  153.         }
  154.     }
  155.    
  156.     /**
  157.      * {@link JexlEngine}を取得する。
  158.      * @return
  159.      */
  160.     public JexlEngine getJexlEngine() {
  161.         return jexlEngine;
  162.     }
  163.    
  164. }