ExprLookup.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.interpol;

  18. import java.util.ArrayList;
  19. import java.util.Objects;

  20. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
  21. import org.apache.commons.configuration2.io.ConfigurationLogger;
  22. import org.apache.commons.jexl2.Expression;
  23. import org.apache.commons.jexl2.JexlContext;
  24. import org.apache.commons.jexl2.JexlEngine;
  25. import org.apache.commons.jexl2.MapContext;
  26. import org.apache.commons.lang3.ClassUtils;
  27. import org.apache.commons.lang3.StringUtils;
  28. import org.apache.commons.text.StringSubstitutor;
  29. import org.apache.commons.text.lookup.StringLookup;

  30. /**
  31.  * Lookup that allows expressions to be evaluated.
  32.  *
  33.  * <pre>
  34.  * ExprLookup.Variables vars = new ExprLookup.Variables();
  35.  * vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class));
  36.  * vars.add(new ExprLookup.Variable("Util", new Utility("Hello")));
  37.  * vars.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
  38.  * XMLConfiguration config = new XMLConfiguration(TEST_FILE);
  39.  * config.setLogger(log);
  40.  * ExprLookup lookup = new ExprLookup(vars);
  41.  * lookup.setConfiguration(config);
  42.  * String str = lookup.lookup("'$[element] ' + String.trimToEmpty('$[space.description]')");
  43.  * </pre>
  44.  *
  45.  * In the example above TEST_FILE contains xml that looks like:
  46.  *
  47.  * <pre>
  48.  * &lt;configuration&gt;
  49.  *   &lt;element&gt;value&lt;/element&gt;
  50.  *   &lt;space xml:space="preserve"&gt;
  51.  *     &lt;description xml:space="default"&gt;     Some text      &lt;/description&gt;
  52.  *   &lt;/space&gt;
  53.  * &lt;/configuration&gt;
  54.  * </pre>
  55.  *
  56.  * The result will be "value Some text".
  57.  *
  58.  * This lookup uses Apache Commons Jexl and requires that the dependency be added to any projects which use this.
  59.  *
  60.  * @since 1.7
  61.  */
  62. public class ExprLookup implements Lookup {
  63.     /**
  64.      * The key and corresponding object that will be made available to the JexlContext for use in expressions.
  65.      */
  66.     public static class Variable {
  67.         /** The name to be used in expressions. */
  68.         private String key;

  69.         /** The object to be accessed in expressions. */
  70.         private Object value;

  71.         /**
  72.          * Constructs a new instance.
  73.          */
  74.         public Variable() {
  75.         }

  76.         /**
  77.          * Constructs a new instance.
  78.          *
  79.          * @param name The name to be used in expressions.
  80.          * @param value The object to be accessed in expressions.
  81.          */
  82.         public Variable(final String name, final Object value) {
  83.             setName(name);
  84.             setValue(value);
  85.         }

  86.         /**
  87.          * Gets the name to be used in expressions.
  88.          *
  89.          * @return the name to be used in expressions.
  90.          */
  91.         public String getName() {
  92.             return key;
  93.         }

  94.         /**
  95.          * Sets the value to be used in expressions.
  96.          *
  97.          * @return the value to be used in expressions.
  98.          */
  99.         public Object getValue() {
  100.             return value;
  101.         }

  102.         /**
  103.          * Sets the name to be used in expressions.
  104.          *
  105.          * @param name the name to be used in expressions.
  106.          */
  107.         public void setName(final String name) {
  108.             this.key = name;
  109.         }

  110.         /**
  111.          * Sets the value to be used in expressions.
  112.          *
  113.          * @param value The object to be accessed in expressions.
  114.          * @throws ConfigurationRuntimeException Wraps an exception creating the value.
  115.          */
  116.         public void setValue(final Object value) throws ConfigurationRuntimeException {
  117.             try {
  118.                 if (!(value instanceof String)) {
  119.                     this.value = value;
  120.                     return;
  121.                 }
  122.                 final String val = (String) value;
  123.                 final String name = StringUtils.removeStartIgnoreCase(val, CLASS);
  124.                 final Class<?> clazz = ClassUtils.getClass(name);
  125.                 if (name.length() == val.length()) {
  126.                     this.value = clazz.getConstructor().newInstance();
  127.                 } else {
  128.                     this.value = clazz;
  129.                 }
  130.             } catch (final Exception e) {
  131.                 throw new ConfigurationRuntimeException("Unable to create " + value, e);
  132.             }

  133.         }
  134.     }

  135.     /**
  136.      * List wrapper used to allow the Variables list to be created as beans in DefaultConfigurationBuilder.
  137.      */
  138.     public static class Variables extends ArrayList<Variable> {
  139.         /**
  140.          * The serial version UID.
  141.          */
  142.         private static final long serialVersionUID = 20111205L;

  143.         /**
  144.          * Creates a new empty instance of {@code Variables}.
  145.          */
  146.         public Variables() {
  147.         }

  148.         /**
  149.          * Creates a new instance of {@code Variables} and copies the content of the given object.
  150.          *
  151.          * @param vars the {@code Variables} object to be copied
  152.          */
  153.         public Variables(final Variables vars) {
  154.             super(vars);
  155.         }

  156.         /**
  157.          * Gets the variable or null if empty.
  158.          *
  159.          * @return the variable or null if empty.
  160.          */
  161.         public Variable getVariable() {
  162.             return !isEmpty() ? get(size() - 1) : null;
  163.         }

  164.     }

  165.     /** Prefix to identify a Java Class object */
  166.     private static final String CLASS = "Class:";

  167.     /** The default prefix for subordinate lookup expressions */
  168.     private static final String DEFAULT_PREFIX = "$[";

  169.     /** The default suffix for subordinate lookup expressions */
  170.     private static final String DEFAULT_SUFFIX = "]";

  171.     /** The ConfigurationInterpolator used by this object. */
  172.     private ConfigurationInterpolator interpolator;

  173.     /** The StringSubstitutor for performing replace operations. */
  174.     private StringSubstitutor substitutor;

  175.     /** The logger used by this instance. */
  176.     private ConfigurationLogger logger;

  177.     /** The engine. */
  178.     private final JexlEngine engine = new JexlEngine();

  179.     /** The variables maintained by this object. */
  180.     private Variables variables;

  181.     /** The String to use to start subordinate lookup expressions */
  182.     private String prefixMatcher = DEFAULT_PREFIX;

  183.     /** The String to use to terminate subordinate lookup expressions */
  184.     private String suffixMatcher = DEFAULT_SUFFIX;

  185.     /**
  186.      * Constructs a new instance. Will get used when the Lookup is constructed via configuration.
  187.      */
  188.     public ExprLookup() {
  189.     }

  190.     /**
  191.      * Constructor for use by applications.
  192.      *
  193.      * @param list The list of objects to be accessible in expressions.
  194.      */
  195.     public ExprLookup(final Variables list) {
  196.         setVariables(list);
  197.     }

  198.     /**
  199.      * Constructor for use by applications.
  200.      *
  201.      * @param list The list of objects to be accessible in expressions.
  202.      * @param prefix The prefix to use for subordinate lookups.
  203.      * @param suffix The suffix to use for subordinate lookups.
  204.      */
  205.     public ExprLookup(final Variables list, final String prefix, final String suffix) {
  206.         this(list);
  207.         setVariablePrefixMatcher(prefix);
  208.         setVariableSuffixMatcher(suffix);
  209.     }

  210.     /**
  211.      * Creates a new {@code JexlContext} and initializes it with the variables managed by this Lookup object.
  212.      *
  213.      * @return the newly created context
  214.      */
  215.     private JexlContext createContext() {
  216.         final JexlContext ctx = new MapContext();
  217.         initializeContext(ctx);
  218.         return ctx;
  219.     }

  220.     /**
  221.      * Gets the {@code ConfigurationInterpolator} used by this object.
  222.      *
  223.      * @return the {@code ConfigurationInterpolator}
  224.      * @since 2.0
  225.      */
  226.     public ConfigurationInterpolator getInterpolator() {
  227.         return interpolator;
  228.     }

  229.     /**
  230.      * Gets the logger used by this object.
  231.      *
  232.      * @return the {@code Log}
  233.      * @since 2.0
  234.      */
  235.     public ConfigurationLogger getLogger() {
  236.         return logger;
  237.     }

  238.     /**
  239.      * Gets the list of Variables that are accessible within expressions. This method returns a copy of the variables
  240.      * managed by this lookup; so modifying this object has no impact on this lookup.
  241.      *
  242.      * @return the List of Variables that are accessible within expressions.
  243.      */
  244.     public Variables getVariables() {
  245.         return new Variables(variables);
  246.     }

  247.     /**
  248.      * Initializes the specified context with the variables managed by this Lookup object.
  249.      *
  250.      * @param ctx the context to be initialized
  251.      */
  252.     private void initializeContext(final JexlContext ctx) {
  253.         variables.forEach(var -> ctx.set(var.getName(), var.getValue()));
  254.     }

  255.     /**
  256.      * Creates a {@code StringSubstitutor} object which uses the passed in {@code ConfigurationInterpolator} as lookup
  257.      * object.
  258.      *
  259.      * @param ip the {@code ConfigurationInterpolator} to be used
  260.      */
  261.     private void installSubstitutor(final ConfigurationInterpolator ip) {
  262.         if (ip == null) {
  263.             substitutor = null;
  264.         } else {
  265.             final StringLookup variableResolver = key -> Objects.toString(ip.resolve(key), null);
  266.             substitutor = new StringSubstitutor(variableResolver, prefixMatcher, suffixMatcher, StringSubstitutor.DEFAULT_ESCAPE);
  267.         }
  268.     }

  269.     /**
  270.      * Evaluates the expression.
  271.      *
  272.      * @param var The expression.
  273.      * @return The String result of the expression.
  274.      */
  275.     @Override
  276.     public String lookup(final String var) {
  277.         if (substitutor == null) {
  278.             return var;
  279.         }

  280.         String result = substitutor.replace(var);
  281.         try {
  282.             final Expression exp = engine.createExpression(result);
  283.             final Object exprResult = exp.evaluate(createContext());
  284.             result = exprResult != null ? String.valueOf(exprResult) : null;
  285.         } catch (final Exception e) {
  286.             final ConfigurationLogger l = getLogger();
  287.             if (l != null) {
  288.                 l.debug("Error encountered evaluating " + result + ": " + e);
  289.             }
  290.         }

  291.         return result;
  292.     }

  293.     /**
  294.      * Sets the {@code ConfigurationInterpolator} to be used by this object.
  295.      *
  296.      * @param interpolator the {@code ConfigurationInterpolator} (may be <strong>null</strong>)
  297.      * @since 2.0
  298.      */
  299.     public void setInterpolator(final ConfigurationInterpolator interpolator) {
  300.         this.interpolator = interpolator;
  301.         installSubstitutor(interpolator);
  302.     }

  303.     /**
  304.      * Sets the logger to be used by this object. If no logger is passed in, no log output is generated.
  305.      *
  306.      * @param logger the {@code Log}
  307.      * @since 2.0
  308.      */
  309.     public void setLogger(final ConfigurationLogger logger) {
  310.         this.logger = logger;
  311.     }

  312.     /**
  313.      * Sets the prefix to use to identify subordinate expressions. This cannot be the same as the prefix used for the primary
  314.      * expression.
  315.      *
  316.      * @param prefix The String identifying the beginning of the expression.
  317.      */
  318.     public void setVariablePrefixMatcher(final String prefix) {
  319.         prefixMatcher = prefix;
  320.     }

  321.     /**
  322.      * Add the Variables that will be accessible within expressions.
  323.      *
  324.      * @param list The list of Variables.
  325.      */
  326.     public void setVariables(final Variables list) {
  327.         variables = new Variables(list);
  328.     }

  329.     /**
  330.      * Sets the suffix to use to identify subordinate expressions. This cannot be the same as the suffix used for the primary
  331.      * expression.
  332.      *
  333.      * @param suffix The String identifying the end of the expression.
  334.      */
  335.     public void setVariableSuffixMatcher(final String suffix) {
  336.         suffixMatcher = suffix;
  337.     }
  338. }