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.configuration2.interpol; 018 019import java.util.ArrayList; 020 021import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 022import org.apache.commons.configuration2.io.ConfigurationLogger; 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.lang3.ClassUtils; 028import org.apache.commons.lang3.StringUtils; 029import org.apache.commons.text.StringSubstitutor; 030import org.apache.commons.text.lookup.StringLookup; 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 */ 066public class ExprLookup implements Lookup 067{ 068 /** Prefix to identify a Java Class object */ 069 private static final String CLASS = "Class:"; 070 071 /** The default prefix for subordinate lookup expressions */ 072 private static final String DEFAULT_PREFIX = "$["; 073 074 /** The default suffix for subordinate lookup expressions */ 075 private static final String DEFAULT_SUFFIX = "]"; 076 077 /** The ConfigurationInterpolator used by this object. */ 078 private ConfigurationInterpolator interpolator; 079 080 /** The StringSubstitutor for performing replace operations. */ 081 private StringSubstitutor substitutor; 082 083 /** The logger used by this instance. */ 084 private ConfigurationLogger logger; 085 086 /** The engine. */ 087 private final JexlEngine engine = new JexlEngine(); 088 089 /** The variables maintained by this object. */ 090 private Variables variables; 091 092 /** The String to use to start subordinate lookup expressions */ 093 private String prefixMatcher = DEFAULT_PREFIX; 094 095 /** The String to use to terminate subordinate lookup expressions */ 096 private String suffixMatcher = DEFAULT_SUFFIX; 097 098 /** 099 * The default constructor. Will get used when the Lookup is constructed via 100 * configuration. 101 */ 102 public ExprLookup() 103 { 104 } 105 106 /** 107 * Constructor for use by applications. 108 * @param list The list of objects to be accessible in expressions. 109 */ 110 public ExprLookup(final Variables list) 111 { 112 setVariables(list); 113 } 114 115 /** 116 * Constructor for use by applications. 117 * @param list The list of objects to be accessible in expressions. 118 * @param prefix The prefix to use for subordinate lookups. 119 * @param suffix The suffix to use for subordinate lookups. 120 */ 121 public ExprLookup(final Variables list, final String prefix, final String suffix) 122 { 123 this(list); 124 setVariablePrefixMatcher(prefix); 125 setVariableSuffixMatcher(suffix); 126 } 127 128 /** 129 * Set the prefix to use to identify subordinate expressions. This cannot be the 130 * same as the prefix used for the primary expression. 131 * @param prefix The String identifying the beginning of the expression. 132 */ 133 public void setVariablePrefixMatcher(final String prefix) 134 { 135 prefixMatcher = prefix; 136 } 137 138 /** 139 * Set the suffix to use to identify subordinate expressions. This cannot be the 140 * same as the suffix used for the primary expression. 141 * @param suffix The String identifying the end of the expression. 142 */ 143 public void setVariableSuffixMatcher(final String suffix) 144 { 145 suffixMatcher = suffix; 146 } 147 148 /** 149 * Add the Variables that will be accessible within expressions. 150 * @param list The list of Variables. 151 */ 152 public void setVariables(final Variables list) 153 { 154 variables = new Variables(list); 155 } 156 157 /** 158 * Returns the list of Variables that are accessible within expressions. 159 * This method returns a copy of the variables managed by this lookup; so 160 * modifying this object has no impact on this lookup. 161 * 162 * @return the List of Variables that are accessible within expressions. 163 */ 164 public Variables getVariables() 165 { 166 return new Variables(variables); 167 } 168 169 /** 170 * Returns the logger used by this object. 171 * 172 * @return the {@code Log} 173 * @since 2.0 174 */ 175 public ConfigurationLogger getLogger() 176 { 177 return logger; 178 } 179 180 /** 181 * Sets the logger to be used by this object. If no logger is passed in, no 182 * log output is generated. 183 * 184 * @param logger the {@code Log} 185 * @since 2.0 186 */ 187 public void setLogger(final ConfigurationLogger logger) 188 { 189 this.logger = logger; 190 } 191 192 /** 193 * Returns the {@code ConfigurationInterpolator} used by this object. 194 * 195 * @return the {@code ConfigurationInterpolator} 196 * @since 2.0 197 */ 198 public ConfigurationInterpolator getInterpolator() 199 { 200 return interpolator; 201 } 202 203 /** 204 * Sets the {@code ConfigurationInterpolator} to be used by this object. 205 * 206 * @param interpolator the {@code ConfigurationInterpolator} (may be 207 * <b>null</b>) 208 * @since 2.0 209 */ 210 public void setInterpolator(final ConfigurationInterpolator interpolator) 211 { 212 this.interpolator = interpolator; 213 installSubstitutor(interpolator); 214 } 215 216 /** 217 * Evaluates the expression. 218 * @param var The expression. 219 * @return The String result of the expression. 220 */ 221 @Override 222 public String lookup(final String var) 223 { 224 if (substitutor == null) 225 { 226 return var; 227 } 228 229 String result = substitutor.replace(var); 230 try 231 { 232 final Expression exp = engine.createExpression(result); 233 final Object exprResult = exp.evaluate(createContext()); 234 result = (exprResult != null) ? String.valueOf(exprResult) : null; 235 } 236 catch (final Exception e) 237 { 238 final ConfigurationLogger l = getLogger(); 239 if (l != null) 240 { 241 l.debug("Error encountered evaluating " + result + ": " + e); 242 } 243 } 244 245 return result; 246 } 247 248 /** 249 * Creates a {@code StringSubstitutor} object which uses the passed in 250 * {@code ConfigurationInterpolator} as lookup object. 251 * 252 * @param ip the {@code ConfigurationInterpolator} to be used 253 */ 254 private void installSubstitutor(final ConfigurationInterpolator ip) 255 { 256 if (ip == null) 257 { 258 substitutor = null; 259 } 260 else 261 { 262 final StringLookup variableResolver = new StringLookup() 263 { 264 @Override 265 public String lookup(final String key) 266 { 267 final Object value = ip.resolve(key); 268 return value != null ? value.toString() : null; 269 } 270 }; 271 substitutor = 272 new StringSubstitutor(variableResolver, prefixMatcher, 273 suffixMatcher, StringSubstitutor.DEFAULT_ESCAPE); 274 } 275 } 276 277 /** 278 * Creates a new {@code JexlContext} and initializes it with the variables 279 * managed by this Lookup object. 280 * 281 * @return the newly created context 282 */ 283 private JexlContext createContext() 284 { 285 final JexlContext ctx = new MapContext(); 286 initializeContext(ctx); 287 return ctx; 288 } 289 290 /** 291 * Initializes the specified context with the variables managed by this 292 * Lookup object. 293 * 294 * @param ctx the context to be initialized 295 */ 296 private void initializeContext(final JexlContext ctx) 297 { 298 for (final Variable var : variables) 299 { 300 ctx.set(var.getName(), var.getValue()); 301 } 302 } 303 304 /** 305 * List wrapper used to allow the Variables list to be created as beans in 306 * DefaultConfigurationBuilder. 307 */ 308 public static class Variables extends ArrayList<Variable> 309 { 310 /** 311 * The serial version UID. 312 */ 313 private static final long serialVersionUID = 20111205L; 314 315 /** 316 * Creates a new empty instance of {@code Variables}. 317 */ 318 public Variables() 319 { 320 super(); 321 } 322 323 /** 324 * Creates a new instance of {@code Variables} and copies the content of 325 * the given object. 326 * 327 * @param vars the {@code Variables} object to be copied 328 */ 329 public Variables(final Variables vars) 330 { 331 super(vars); 332 } 333 334 public Variable getVariable() 335 { 336 return size() > 0 ? get(size() - 1) : null; 337 } 338 339 } 340 341 /** 342 * The key and corresponding object that will be made available to the 343 * JexlContext for use in expressions. 344 */ 345 public static class Variable 346 { 347 /** The name to be used in expressions */ 348 private String key; 349 350 /** The object to be accessed in expressions */ 351 private Object value; 352 353 public Variable() 354 { 355 } 356 357 public Variable(final String name, final Object value) 358 { 359 setName(name); 360 setValue(value); 361 } 362 363 public String getName() 364 { 365 return key; 366 } 367 368 public void setName(final String name) 369 { 370 this.key = name; 371 } 372 373 public Object getValue() 374 { 375 return value; 376 } 377 378 public void setValue(final Object value) throws ConfigurationRuntimeException 379 { 380 try 381 { 382 if (!(value instanceof String)) 383 { 384 this.value = value; 385 return; 386 } 387 final String val = (String) value; 388 final String name = StringUtils.removeStartIgnoreCase(val, CLASS); 389 final Class<?> clazz = ClassUtils.getClass(name); 390 if (name.length() == val.length()) 391 { 392 this.value = clazz.newInstance(); 393 } 394 else 395 { 396 this.value = clazz; 397 } 398 } 399 catch (final Exception e) 400 { 401 throw new ConfigurationRuntimeException("Unable to create " + value, e); 402 } 403 404 } 405 } 406}