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