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