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 * <configuration>
050 * <element>value</element>
051 * <space xml:space="preserve">
052 * <description xml:space="default"> Some text </description>
053 * </space>
054 * </configuration>
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 }