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 */
017package org.apache.commons.configuration.interpol;
018
019import java.util.ArrayList;
020
021import org.apache.commons.configuration.AbstractConfiguration;
022import org.apache.commons.configuration.ConfigurationRuntimeException;
023import org.apache.commons.jexl2.Expression;
024import org.apache.commons.jexl2.JexlContext;
025import org.apache.commons.jexl2.JexlEngine;
026import org.apache.commons.jexl2.MapContext;
027import org.apache.commons.lang.ClassUtils;
028import org.apache.commons.lang.StringUtils;
029import org.apache.commons.lang.text.StrLookup;
030import 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 */
067public 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}