1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.configuration.interpol; 18 19 import java.util.ArrayList; 20 21 import org.apache.commons.configuration.AbstractConfiguration; 22 import org.apache.commons.configuration.ConfigurationRuntimeException; 23 import org.apache.commons.jexl2.Expression; 24 import org.apache.commons.jexl2.JexlContext; 25 import org.apache.commons.jexl2.JexlEngine; 26 import org.apache.commons.jexl2.MapContext; 27 import org.apache.commons.lang.ClassUtils; 28 import org.apache.commons.lang.StringUtils; 29 import org.apache.commons.lang.text.StrLookup; 30 import org.apache.commons.lang.text.StrSubstitutor; 31 32 /** 33 * Lookup that allows expressions to be evaluated. 34 * 35 * <pre> 36 * ExprLookup.Variables vars = new ExprLookup.Variables(); 37 * vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class)); 38 * vars.add(new ExprLookup.Variable("Util", new Utility("Hello"))); 39 * vars.add(new ExprLookup.Variable("System", "Class:java.lang.System")); 40 * XMLConfiguration config = new XMLConfiguration(TEST_FILE); 41 * config.setLogger(log); 42 * ExprLookup lookup = new ExprLookup(vars); 43 * lookup.setConfiguration(config); 44 * String str = lookup.lookup("'$[element] ' + String.trimToEmpty('$[space.description]')"); 45 * </pre> 46 * 47 * In the example above TEST_FILE contains xml that looks like: 48 * <pre> 49 * <configuration> 50 * <element>value</element> 51 * <space xml:space="preserve"> 52 * <description xml:space="default"> Some text </description> 53 * </space> 54 * </configuration> 55 * </pre> 56 * 57 * The result will be "value Some text". 58 * 59 * This lookup uses Apache Commons Jexl and requires that the dependency be added to any 60 * projects which use this. 61 * 62 * @since 1.7 63 * @author <a 64 * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a> 65 * @version $Id: ExprLookup.java 1234539 2012-01-22 16:19:15Z oheger $ 66 */ 67 public class ExprLookup extends StrLookup 68 { 69 /** Prefix to identify a Java Class object */ 70 private static final String CLASS = "Class:"; 71 72 /** The default prefix for subordinate lookup expressions */ 73 private static final String DEFAULT_PREFIX = "$["; 74 75 /** The default suffix for subordinate lookup expressions */ 76 private static final String DEFAULT_SUFFIX = "]"; 77 78 /** Configuration being operated on */ 79 private AbstractConfiguration configuration; 80 81 /** The engine. */ 82 private final JexlEngine engine = new JexlEngine(); 83 84 /** The variables maintained by this object. */ 85 private Variables variables; 86 87 /** The String to use to start subordinate lookup expressions */ 88 private String prefixMatcher = DEFAULT_PREFIX; 89 90 /** The String to use to terminate subordinate lookup expressions */ 91 private String suffixMatcher = DEFAULT_SUFFIX; 92 93 /** 94 * The default constructor. Will get used when the Lookup is constructed via 95 * configuration. 96 */ 97 public ExprLookup() 98 { 99 } 100 101 /** 102 * Constructor for use by applications. 103 * @param list The list of objects to be accessible in expressions. 104 */ 105 public ExprLookup(Variables list) 106 { 107 setVariables(list); 108 } 109 110 /** 111 * Constructor for use by applications. 112 * @param list The list of objects to be accessible in expressions. 113 * @param prefix The prefix to use for subordinate lookups. 114 * @param suffix The suffix to use for subordinate lookups. 115 */ 116 public ExprLookup(Variables list, String prefix, String suffix) 117 { 118 this(list); 119 setVariablePrefixMatcher(prefix); 120 setVariableSuffixMatcher(suffix); 121 } 122 123 /** 124 * Set the prefix to use to identify subordinate expressions. This cannot be the 125 * same as the prefix used for the primary expression. 126 * @param prefix The String identifying the beginning of the expression. 127 */ 128 public void setVariablePrefixMatcher(String prefix) 129 { 130 prefixMatcher = prefix; 131 } 132 133 134 /** 135 * Set the suffix to use to identify subordinate expressions. This cannot be the 136 * same as the suffix used for the primary expression. 137 * @param suffix The String identifying the end of the expression. 138 */ 139 public void setVariableSuffixMatcher(String suffix) 140 { 141 suffixMatcher = suffix; 142 } 143 144 /** 145 * Add the Variables that will be accessible within expressions. 146 * @param list The list of Variables. 147 */ 148 public void setVariables(Variables list) 149 { 150 variables = new Variables(list); 151 } 152 153 /** 154 * Returns the list of Variables that are accessible within expressions. 155 * @return the List of Variables that are accessible within expressions. 156 */ 157 public Variables getVariables() 158 { 159 return null; 160 } 161 162 /** 163 * Set the configuration to be used to interpolate subordinate expressions. 164 * @param config The Configuration. 165 */ 166 public void setConfiguration(AbstractConfiguration config) 167 { 168 this.configuration = config; 169 } 170 171 /** 172 * Evaluates the expression. 173 * @param var The expression. 174 * @return The String result of the expression. 175 */ 176 @Override 177 public String lookup(String var) 178 { 179 ConfigurationInterpolator interp = configuration.getInterpolator(); 180 StrSubstitutor subst = new StrSubstitutor(interp, prefixMatcher, suffixMatcher, 181 StrSubstitutor.DEFAULT_ESCAPE); 182 183 String result = subst.replace(var); 184 185 try 186 { 187 Expression exp = engine.createExpression(result); 188 result = (String) exp.evaluate(createContext()); 189 } 190 catch (Exception e) 191 { 192 configuration.getLogger().debug("Error encountered evaluating " + result, e); 193 } 194 195 return result; 196 } 197 198 /** 199 * Creates a new {@code JexlContext} and initializes it with the variables 200 * managed by this Lookup object. 201 * 202 * @return the newly created context 203 */ 204 private JexlContext createContext() 205 { 206 JexlContext ctx = new MapContext(); 207 initializeContext(ctx); 208 return ctx; 209 } 210 211 /** 212 * Initializes the specified context with the variables managed by this 213 * Lookup object. 214 * 215 * @param ctx the context to be initialized 216 */ 217 private void initializeContext(JexlContext ctx) 218 { 219 for (Variable var : variables) 220 { 221 ctx.set(var.getName(), var.getValue()); 222 } 223 } 224 225 /** 226 * List wrapper used to allow the Variables list to be created as beans in 227 * DefaultConfigurationBuilder. 228 */ 229 public static class Variables extends ArrayList<Variable> 230 { 231 /** 232 * The serial version UID. 233 */ 234 private static final long serialVersionUID = 20111205L; 235 236 /** 237 * Creates a new empty instance of {@code Variables}. 238 */ 239 public Variables() 240 { 241 super(); 242 } 243 244 /** 245 * Creates a new instance of {@code Variables} and copies the content of 246 * the given object. 247 * 248 * @param vars the {@code Variables} object to be copied 249 */ 250 public Variables(Variables vars) 251 { 252 super(vars); 253 } 254 255 public Variable getVariable() 256 { 257 if (size() > 0) 258 { 259 return get(size() - 1); 260 } 261 else 262 { 263 return null; 264 } 265 } 266 267 } 268 269 /** 270 * The key and corresponding object that will be made available to the 271 * JexlContext for use in expressions. 272 */ 273 public static class Variable 274 { 275 /** The name to be used in expressions */ 276 private String key; 277 278 /** The object to be accessed in expressions */ 279 private Object value; 280 281 public Variable() 282 { 283 } 284 285 public Variable(String name, Object value) 286 { 287 setName(name); 288 setValue(value); 289 } 290 291 public String getName() 292 { 293 return key; 294 } 295 296 public void setName(String name) 297 { 298 this.key = name; 299 } 300 301 public Object getValue() 302 { 303 return value; 304 } 305 306 public void setValue(Object value) throws ConfigurationRuntimeException 307 { 308 try 309 { 310 if (!(value instanceof String)) 311 { 312 this.value = value; 313 return; 314 } 315 String val = (String) value; 316 String name = StringUtils.removeStartIgnoreCase(val, CLASS); 317 Class<?> clazz = ClassUtils.getClass(name); 318 if (name.length() == val.length()) 319 { 320 this.value = clazz.newInstance(); 321 } 322 else 323 { 324 this.value = clazz; 325 } 326 } 327 catch (Exception e) 328 { 329 throw new ConfigurationRuntimeException("Unable to create " + value, e); 330 } 331 332 } 333 } 334 }