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 * @version $Id: ExprLookup.java 1842196 2018-09-27 22:30:20Z ggregory $ 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 StringSubstitutor for performing replace operations. */ 082 private StringSubstitutor 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(final 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(final Variables list, final String prefix, final 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(final 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(final 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(final 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(final 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(final 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(final String var) 224 { 225 if (substitutor == null) 226 { 227 return var; 228 } 229 230 String result = substitutor.replace(var); 231 try 232 { 233 final Expression exp = engine.createExpression(result); 234 final Object exprResult = exp.evaluate(createContext()); 235 result = (exprResult != null) ? String.valueOf(exprResult) : null; 236 } 237 catch (final Exception e) 238 { 239 final 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 StringSubstitutor} 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 final StringLookup variableResolver = new StringLookup() 264 { 265 @Override 266 public String lookup(final String key) 267 { 268 final Object value = ip.resolve(key); 269 return value != null ? value.toString() : null; 270 } 271 }; 272 substitutor = 273 new StringSubstitutor(variableResolver, prefixMatcher, 274 suffixMatcher, StringSubstitutor.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 final 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(final JexlContext ctx) 298 { 299 for (final 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(final Variables vars) 331 { 332 super(vars); 333 } 334 335 public Variable getVariable() 336 { 337 return size() > 0 ? get(size() - 1) : null; 338 } 339 340 } 341 342 /** 343 * The key and corresponding object that will be made available to the 344 * JexlContext for use in expressions. 345 */ 346 public static class Variable 347 { 348 /** The name to be used in expressions */ 349 private String key; 350 351 /** The object to be accessed in expressions */ 352 private Object value; 353 354 public Variable() 355 { 356 } 357 358 public Variable(final String name, final Object value) 359 { 360 setName(name); 361 setValue(value); 362 } 363 364 public String getName() 365 { 366 return key; 367 } 368 369 public void setName(final String name) 370 { 371 this.key = name; 372 } 373 374 public Object getValue() 375 { 376 return value; 377 } 378 379 public void setValue(final Object value) throws ConfigurationRuntimeException 380 { 381 try 382 { 383 if (!(value instanceof String)) 384 { 385 this.value = value; 386 return; 387 } 388 final String val = (String) value; 389 final String name = StringUtils.removeStartIgnoreCase(val, CLASS); 390 final Class<?> clazz = ClassUtils.getClass(name); 391 if (name.length() == val.length()) 392 { 393 this.value = clazz.newInstance(); 394 } 395 else 396 { 397 this.value = clazz; 398 } 399 } 400 catch (final Exception e) 401 { 402 throw new ConfigurationRuntimeException("Unable to create " + value, e); 403 } 404 405 } 406 } 407}