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