    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.configuration.interpol;
    018
    019    import java.util.ArrayList;
    020
    021    import org.apache.commons.configuration.AbstractConfiguration;
    022    import org.apache.commons.configuration.ConfigurationRuntimeException;
    023    import org.apache.commons.jexl2.Expression;
    024    import org.apache.commons.jexl2.JexlContext;
    025    import org.apache.commons.jexl2.JexlEngine;
    026    import org.apache.commons.jexl2.MapContext;
    027    import org.apache.commons.lang.ClassUtils;
    028    import org.apache.commons.lang.StringUtils;
    029    import org.apache.commons.lang.text.StrLookup;
    030    import org.apache.commons.lang.text.StrSubstitutor;
    031
    032    /**
    033     * Lookup that allows expressions to be evaluated.
    034     *
    035     * <pre>
    036     *     ExprLookup.Variables vars = new ExprLookup.Variables();
    037     *     vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class));
    038     *     vars.add(new ExprLookup.Variable("Util", new Utility("Hello")));
    039     *     vars.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
    040     *     XMLConfiguration config = new XMLConfiguration(TEST_FILE);
    041     *     config.setLogger(log);
    042     *     ExprLookup lookup = new ExprLookup(vars);
    043     *     lookup.setConfiguration(config);
    044     *     String str = lookup.lookup("'$[element] ' + String.trimToEmpty('$[space.description]')");
    045     * </pre>
    046     *
    047     * In the example above TEST_FILE contains xml that looks like:
    048     * <pre>
    049     * &lt;configuration&gt;
    050     *   &lt;element&gt;value&lt;/element&gt;
    051     *   &lt;space xml:space="preserve"&gt;
    052     *     &lt;description xml:space="default"&gt;     Some text      &lt;/description&gt;
    053     *   &lt;/space&gt;
    054     * &lt;/configuration&gt;
    055     * </pre>
    056     *
    057     * The result will be "value Some text".
    058     *
    059     * This lookup uses Apache Commons Jexl and requires that the dependency be added to any
    060     * projects which use this.
    061     *
    062     * @since 1.7
    063     * @author <a
    064     * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
    065     * @version $Id: ExprLookup.java 1234539 2012-01-22 16:19:15Z oheger $
    066     */
    067    public class ExprLookup extends StrLookup
    068    {
    069        /** Prefix to identify a Java Class object */
    070        private static final String CLASS = "Class:";
    071
    072        /** The default prefix for subordinate lookup expressions */
    073        private static final String DEFAULT_PREFIX = "$[";
    074
    075        /** The default suffix for subordinate lookup expressions */
    076        private static final String DEFAULT_SUFFIX = "]";
    077
    078        /** Configuration being operated on */
    079        private AbstractConfiguration configuration;
    080
    081        /** The engine. */
    082        private final JexlEngine engine = new JexlEngine();
    083
    084        /** The variables maintained by this object. */
    085        private Variables variables;
    086
    087        /** The String to use to start subordinate lookup expressions */
    088        private String prefixMatcher = DEFAULT_PREFIX;
    089
    090        /** The String to use to terminate subordinate lookup expressions */
    091        private String suffixMatcher = DEFAULT_SUFFIX;
    092
    093        /**
    094         * The default constructor. Will get used when the Lookup is constructed via
    095         * configuration.
    096         */
    097        public ExprLookup()
    098        {
    099        }
    100
    101        /**
    102         * Constructor for use by applications.
    103         * @param list The list of objects to be accessible in expressions.
    104         */
    105        public ExprLookup(Variables list)
    106        {
    107            setVariables(list);
    108        }
    109
    110        /**
    111         * Constructor for use by applications.
    112         * @param list The list of objects to be accessible in expressions.
    113         * @param prefix The prefix to use for subordinate lookups.
    114         * @param suffix The suffix to use for subordinate lookups.
    115         */
    116        public ExprLookup(Variables list, String prefix, String suffix)
    117        {
    118            this(list);
    119            setVariablePrefixMatcher(prefix);
    120            setVariableSuffixMatcher(suffix);
    121        }
    122
    123        /**
    124         * Set the prefix to use to identify subordinate expressions. This cannot be the
    125         * same as the prefix used for the primary expression.
    126         * @param prefix The String identifying the beginning of the expression.
    127         */
    128        public void setVariablePrefixMatcher(String prefix)
    129        {
    130            prefixMatcher = prefix;
    131        }
    132
    133
    134        /**
    135         * Set the suffix to use to identify subordinate expressions. This cannot be the
    136         * same as the suffix used for the primary expression.
    137         * @param suffix The String identifying the end of the expression.
    138         */
    139        public void setVariableSuffixMatcher(String suffix)
    140        {
    141            suffixMatcher = suffix;
    142        }
    143
    144        /**
    145         * Add the Variables that will be accessible within expressions.
    146         * @param list The list of Variables.
    147         */
    148        public void setVariables(Variables list)
    149        {
    150            variables = new Variables(list);
    151        }
    152
    153        /**
    154         * Returns the list of Variables that are accessible within expressions.
    155         * @return the List of Variables that are accessible within expressions.
    156         */
    157        public Variables getVariables()
    158        {
    159            return null;
    160        }
    161
    162        /**
    163         * Set the configuration to be used to interpolate subordinate expressions.
    164         * @param config The Configuration.
    165         */
    166        public void setConfiguration(AbstractConfiguration config)
    167        {
    168            this.configuration = config;
    169        }
    170
    171        /**
    172         * Evaluates the expression.
    173         * @param var The expression.
    174         * @return The String result of the expression.
    175         */
    176        @Override
    177        public String lookup(String var)
    178        {
    179            ConfigurationInterpolator interp = configuration.getInterpolator();
    180            StrSubstitutor subst = new StrSubstitutor(interp, prefixMatcher, suffixMatcher,
    181                    StrSubstitutor.DEFAULT_ESCAPE);
    182
    183            String result = subst.replace(var);
    184
    185            try
    186            {
    187                Expression exp = engine.createExpression(result);
    188                result = (String) exp.evaluate(createContext());
    189            }
    190            catch (Exception e)
    191            {
    192                configuration.getLogger().debug("Error encountered evaluating " + result, e);
    193            }
    194
    195            return result;
    196        }
    197
    198        /**
    199         * Creates a new {@code JexlContext} and initializes it with the variables
    200         * managed by this Lookup object.
    201         *
    202         * @return the newly created context
    203         */
    204        private JexlContext createContext()
    205        {
    206            JexlContext ctx = new MapContext();
    207            initializeContext(ctx);
    208            return ctx;
    209        }
    210
    211        /**
    212         * Initializes the specified context with the variables managed by this
    213         * Lookup object.
    214         *
    215         * @param ctx the context to be initialized
    216         */
    217        private void initializeContext(JexlContext ctx)
    218        {
    219            for (Variable var : variables)
    220            {
    221                ctx.set(var.getName(), var.getValue());
    222            }
    223        }
    224
    225        /**
    226         * List wrapper used to allow the Variables list to be created as beans in
    227         * DefaultConfigurationBuilder.
    228         */
    229        public static class Variables extends ArrayList<Variable>
    230        {
    231            /**
    232             * The serial version UID.
    233             */
    234            private static final long serialVersionUID = 20111205L;
    235
    236            /**
    237             * Creates a new empty instance of {@code Variables}.
    238             */
    239            public Variables()
    240            {
    241                super();
    242            }
    243
    244            /**
    245             * Creates a new instance of {@code Variables} and copies the content of
    246             * the given object.
    247             *
    248             * @param vars the {@code Variables} object to be copied
    249             */
    250            public Variables(Variables vars)
    251            {
    252                super(vars);
    253            }
    254
    255            public Variable getVariable()
    256            {
    257                if (size() > 0)
    258                {
    259                    return get(size() - 1);
    260                }
    261                else
    262                {
    263                    return null;
    264                }
    265            }
    266
    267        }
    268
    269        /**
    270         * The key and corresponding object that will be made available to the
    271         * JexlContext for use in expressions.
    272         */
    273        public static class Variable
    274        {
    275            /** The name to be used in expressions */
    276            private String key;
    277
    278            /** The object to be accessed in expressions */
    279            private Object value;
    280
    281            public Variable()
    282            {
    283            }
    284
    285            public Variable(String name, Object value)
    286            {
    287                setName(name);
    288                setValue(value);
    289            }
    290
    291            public String getName()
    292            {
    293                return key;
    294            }
    295
    296            public void setName(String name)
    297            {
    298                this.key = name;
    299            }
    300
    301            public Object getValue()
    302            {
    303                return value;
    304            }
    305
    306            public void setValue(Object value) throws ConfigurationRuntimeException
    307            {
    308                try
    309                {
    310                    if (!(value instanceof String))
    311                    {
    312                        this.value = value;
    313                        return;
    314                    }
    315                    String val = (String) value;
    316                    String name = StringUtils.removeStartIgnoreCase(val, CLASS);
    317                    Class<?> clazz = ClassUtils.getClass(name);
    318                    if (name.length() == val.length())
    319                    {
    320                        this.value = clazz.newInstance();
    321                    }
    322                    else
    323                    {
    324                        this.value = clazz;
    325                    }
    326                }
    327                catch (Exception e)
    328                {
    329                    throw new ConfigurationRuntimeException("Unable to create " + value, e);
    330                }
    331
    332            }
    333        }
    334    }