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    }