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.configuration.interpol; 018 019import java.util.ArrayList; 020 021import org.apache.commons.configuration.AbstractConfiguration; 022import org.apache.commons.configuration.ConfigurationRuntimeException; 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.lang.ClassUtils; 028import org.apache.commons.lang.StringUtils; 029import org.apache.commons.lang.text.StrLookup; 030import org.apache.commons.lang.text.StrSubstitutor; 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 1234539 2012-01-22 16:19:15Z oheger $ 066 */ 067public class ExprLookup extends StrLookup 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 /** Configuration being operated on */ 079 private AbstractConfiguration configuration; 080 081 /** The engine. */ 082 private final JexlEngine engine = new JexlEngine(); 083 084 /** The variables maintained by this object. */ 085 private Variables variables; 086 087 /** The String to use to start subordinate lookup expressions */ 088 private String prefixMatcher = DEFAULT_PREFIX; 089 090 /** The String to use to terminate subordinate lookup expressions */ 091 private String suffixMatcher = DEFAULT_SUFFIX; 092 093 /** 094 * The default constructor. Will get used when the Lookup is constructed via 095 * configuration. 096 */ 097 public ExprLookup() 098 { 099 } 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}