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