001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.krad.uif.service.impl; 017 018 import net.sf.ehcache.CacheManager; 019 import net.sf.ehcache.Cache; 020 import net.sf.ehcache.Element; 021 import org.apache.commons.lang.StringUtils; 022 import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean; 023 import org.kuali.rice.krad.uif.UifConstants; 024 import org.kuali.rice.krad.uif.component.BindingInfo; 025 import org.kuali.rice.krad.uif.component.Component; 026 import org.kuali.rice.krad.uif.component.KeepExpression; 027 import org.kuali.rice.krad.uif.component.PropertyReplacer; 028 import org.kuali.rice.krad.uif.container.CollectionGroup; 029 import org.kuali.rice.krad.uif.field.DataField; 030 import org.kuali.rice.krad.uif.layout.LayoutManager; 031 import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService; 032 import org.kuali.rice.krad.uif.util.CloneUtils; 033 import org.kuali.rice.krad.uif.util.ExpressionFunctions; 034 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 035 import org.kuali.rice.krad.uif.view.View; 036 import org.springframework.expression.Expression; 037 import org.springframework.expression.ExpressionParser; 038 import org.springframework.expression.common.TemplateParserContext; 039 import org.springframework.expression.spel.standard.SpelExpressionParser; 040 import org.springframework.expression.spel.support.StandardEvaluationContext; 041 042 import java.lang.reflect.Method; 043 import java.util.Collection; 044 import java.util.List; 045 import java.util.Map; 046 import java.util.Map.Entry; 047 048 /** 049 * Evaluates expression language statements using the Spring EL engine 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053 public class ExpressionEvaluatorServiceImpl implements ExpressionEvaluatorService { 054 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( 055 ExpressionEvaluatorServiceImpl.class); 056 057 protected static ExpressionParser parser = new SpelExpressionParser(); 058 059 static{ 060 CacheManager singletonManager = CacheManager.create(); 061 singletonManager.addCache(new Cache("ExpressionEvaluatorServiceCache", 1500, false, false, 120, 0)); 062 } 063 064 Cache expressionCache = CacheManager.getInstance().getCache("ExpressionEvaluatorServiceCache"); 065 066 private static Method isAssignableFrom; 067 private static Method empty; 068 private static Method emptyList; 069 private static Method listContains; 070 private static Method getName; 071 private static Method getParm; 072 private static Method getParmInd; 073 private static Method hasPerm; 074 private static Method hasPermDtls; 075 private static Method hasPermTmpl; 076 private static Method sequence; 077 078 static{ 079 try{ 080 isAssignableFrom = ExpressionFunctions.class.getDeclaredMethod("isAssignableFrom", new Class[]{Class.class, Class.class}); 081 empty = ExpressionFunctions.class.getDeclaredMethod("empty", new Class[]{Object.class}); 082 emptyList = ExpressionFunctions.class.getDeclaredMethod("emptyList", new Class[]{List.class}); 083 listContains = ExpressionFunctions.class.getDeclaredMethod("listContains", new Class[]{List.class, Object[].class}); 084 getName = ExpressionFunctions.class.getDeclaredMethod("getName", new Class[]{Class.class}); 085 getParm = ExpressionFunctions.class.getDeclaredMethod("getParm", new Class[]{String.class, String.class, String.class}); 086 getParmInd = ExpressionFunctions.class.getDeclaredMethod("getParmInd", new Class[]{String.class, String.class, String.class}); 087 hasPerm = ExpressionFunctions.class.getDeclaredMethod("hasPerm", new Class[]{String.class, String.class}); 088 hasPermDtls = ExpressionFunctions.class.getDeclaredMethod("hasPermDtls", new Class[]{String.class, String.class, Map.class, Map.class}); 089 hasPermTmpl = ExpressionFunctions.class.getDeclaredMethod("hasPermTmpl", new Class[]{String.class, String.class, Map.class, Map.class}); 090 sequence = ExpressionFunctions.class.getDeclaredMethod("sequence", new Class[]{String.class}); 091 }catch(NoSuchMethodException e){ 092 LOG.error("Custom function for el expressions not found: " + e.getMessage()); 093 throw new RuntimeException("Custom function for el expressions not found: " + e.getMessage(), e); 094 } 095 } 096 097 /** 098 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionsOnConfigurable(org.kuali.rice.krad.uif.view.View, 099 * org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean, Object, 100 * java.util.Map<String,Object>) 101 */ 102 public void evaluateExpressionsOnConfigurable(View view, UifDictionaryBean expressionConfigurable, 103 StandardEvaluationContext context) { 104 if ((expressionConfigurable instanceof Component) || (expressionConfigurable instanceof LayoutManager)) { 105 evaluatePropertyReplacers(view, expressionConfigurable, context); 106 } 107 evaluatePropertyExpressions(view, expressionConfigurable, context); 108 } 109 110 public void evaluateExpressionsOnConfigurable(View view, UifDictionaryBean expressionConfigurable, 111 Object contextObject, Map<String, Object> evaluationParameters) { 112 evaluateExpressionsOnConfigurable(view, expressionConfigurable, getContext(contextObject,evaluationParameters)); 113 } 114 115 public String evaluateExpressionTemplate(Object contextObject, Map<String, Object> evaluationParameters, 116 String expressionTemplate) { 117 return evaluateExpressionTemplate(getContext(contextObject,evaluationParameters),expressionTemplate); 118 } 119 /** 120 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionTemplate(Object, 121 * java.util.Map, String) 122 */ 123 public String evaluateExpressionTemplate(StandardEvaluationContext context, 124 String expressionTemplate) { 125 126 String result = null; 127 try { 128 Expression expression = null; 129 if (StringUtils.contains(expressionTemplate, UifConstants.EL_PLACEHOLDER_PREFIX)) { 130 expression = getTemplateExpression(expressionTemplate); 131 } else { 132 expression = getExpression(expressionTemplate); 133 } 134 135 result = expression.getValue(context, String.class); 136 } catch (Exception e) { 137 LOG.error("Exception evaluating expression: " + expressionTemplate); 138 throw new RuntimeException("Exception evaluating expression: " + expressionTemplate, e); 139 } 140 141 return result; 142 } 143 144 public Object evaluateExpression(Object contextObject, Map<String, Object> evaluationParameters, 145 String expressionStr) { 146 return evaluateExpression(getContext(contextObject,evaluationParameters),expressionStr); 147 } 148 149 /** 150 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpression(Object, 151 * java.util.Map, String) 152 */ 153 public Object evaluateExpression(StandardEvaluationContext context, 154 String expressionStr) { 155 156 // if expression contains placeholders remove before evaluating 157 if (StringUtils.startsWith(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith( 158 expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX)) { 159 expressionStr = StringUtils.removeStart(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX); 160 expressionStr = StringUtils.removeEnd(expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX); 161 } 162 163 164 Object result = null; 165 try { 166 Expression expression = getExpression(expressionStr); 167 168 result = expression.getValue(context); 169 } catch (Exception e) { 170 LOG.error("Exception evaluating expression: " + expressionStr); 171 throw new RuntimeException("Exception evaluating expression: " + expressionStr, e); 172 } 173 174 return result; 175 } 176 177 public StandardEvaluationContext getContext(Object contextObject, Map<String, Object> evaluationParameters){ 178 StandardEvaluationContext context = new StandardEvaluationContext(contextObject); 179 context.setVariables(evaluationParameters); 180 addCustomFunctions(context); 181 return context; 182 } 183 184 private Expression getTemplateExpression(String expressionTemplate) { 185 Element cachedResult = expressionCache.get(expressionTemplate); 186 Object result; 187 if(cachedResult==null){ 188 result = parser.parseExpression(expressionTemplate, new TemplateParserContext( 189 UifConstants.EL_PLACEHOLDER_PREFIX, UifConstants.EL_PLACEHOLDER_SUFFIX));; 190 expressionCache.put(new Element(expressionTemplate, result)); 191 }else{ 192 result = cachedResult.getObjectValue(); 193 } 194 return (Expression)result; 195 196 } 197 198 private Expression getExpression(String expressionStr) { 199 Element cachedResult = expressionCache.get(expressionStr); 200 Object result; 201 if(cachedResult==null){ 202 result = parser.parseExpression(expressionStr); 203 expressionCache.put(new Element(expressionStr,result)); 204 }else{ 205 result = cachedResult.getObjectValue(); 206 } 207 return (Expression)result; 208 } 209 210 /** 211 * Registers custom functions for el expressions with the given context 212 * 213 * @param context - context instance to register functions to 214 */ 215 protected void addCustomFunctions(StandardEvaluationContext context) { 216 // TODO: possibly reflect ExpressionFunctions and add automatically 217 context.registerFunction("isAssignableFrom", isAssignableFrom); 218 context.registerFunction("empty", empty); 219 context.registerFunction("emptyList", emptyList); 220 context.registerFunction("listContains", listContains); 221 context.registerFunction("getName", getName); 222 context.registerFunction("getParm", getParm); 223 context.registerFunction("getParmInd", getParmInd); 224 context.registerFunction("hasPerm", hasPerm); 225 context.registerFunction("hasPermDtls", hasPermDtls); 226 context.registerFunction("hasPermTmpl", hasPermTmpl); 227 context.registerFunction("sequence", sequence); 228 229 } 230 231 /** 232 * Iterates through any configured <code>PropertyReplacer</code> instances for the component and 233 * evaluates the given condition. If the condition is met, the replacement value is set on the 234 * corresponding property 235 * 236 * @param view - view instance being rendered 237 * @param expressionConfigurable - expressionConfigurable instance with property replacers list, should be either a 238 * component or layout 239 * manager 240 * @param context - context for el evaluation 241 */ 242 protected void evaluatePropertyReplacers(View view, UifDictionaryBean expressionConfigurable, 243 StandardEvaluationContext context) { 244 List<PropertyReplacer> replacers = null; 245 if (Component.class.isAssignableFrom(expressionConfigurable.getClass())) { 246 replacers = ((Component) expressionConfigurable).getPropertyReplacers(); 247 } else if (LayoutManager.class.isAssignableFrom(expressionConfigurable.getClass())) { 248 replacers = ((LayoutManager) expressionConfigurable).getPropertyReplacers(); 249 } 250 251 for (PropertyReplacer propertyReplacer : replacers) { 252 String expression = propertyReplacer.getCondition(); 253 String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression); 254 255 String conditionEvaluation = evaluateExpressionTemplate(context, 256 adjustedExpression); 257 boolean conditionSuccess = Boolean.parseBoolean(conditionEvaluation); 258 if (conditionSuccess) { 259 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyReplacer.getPropertyName(), 260 propertyReplacer.getReplacement()); 261 } 262 } 263 } 264 protected void evaluatePropertyReplacers(View view, UifDictionaryBean expressionConfigurable, Object contextObject, 265 Map<String, Object> evaluationParameters) { 266 evaluatePropertyReplacers(view,expressionConfigurable,getContext(contextObject,evaluationParameters)); 267 } 268 269 /** 270 * Retrieves the Map from the given object that containing the property expressions that should 271 * be evaluated. Each expression is then evaluated and the result is used to set the property value 272 * 273 * <p> 274 * If the expression is an el template (part static text and part expression), only the expression 275 * part will be replaced with the result. More than one expressions may be contained within the template 276 * </p> 277 * 278 * @param view - view instance that is being rendered 279 * @param expressionConfigurable - object instance to evaluate expressions for 280 * @param context - object providing the default context for expressions 281 */ 282 protected void evaluatePropertyExpressions(View view, UifDictionaryBean expressionConfigurable, 283 StandardEvaluationContext context) { 284 Map<String, String> propertyExpressions = expressionConfigurable.getPropertyExpressions(); 285 for (Entry<String, String> propertyExpression : propertyExpressions.entrySet()) { 286 String propertyName = propertyExpression.getKey(); 287 String expression = propertyExpression.getValue(); 288 289 // check whether expression should be evaluated or property should retain the expression 290 if (CloneUtils.fieldHasAnnotation(expressionConfigurable.getClass(), propertyName, KeepExpression.class)) { 291 // set expression as property value to be handled by the component 292 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, expression); 293 continue; 294 } 295 296 Object propertyValue = null; 297 298 // replace binding prefixes (lp, dp, fp) in expression before evaluation 299 String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression); 300 301 // determine whether the expression is a string template, or evaluates to another object type 302 if (StringUtils.startsWith(adjustedExpression, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith( 303 adjustedExpression, UifConstants.EL_PLACEHOLDER_SUFFIX) && (StringUtils.countMatches( 304 adjustedExpression, UifConstants.EL_PLACEHOLDER_PREFIX) == 1)) { 305 propertyValue = evaluateExpression(context, adjustedExpression); 306 } else { 307 // treat as string template 308 propertyValue = evaluateExpressionTemplate(context, adjustedExpression); 309 } 310 311 // if property name has the special indicator then we need to add the expression result to the property 312 // value instead of replace 313 if (StringUtils.endsWith(propertyName, ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR)) { 314 StringUtils.removeEnd(propertyName, ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR); 315 316 Collection collectionValue = ObjectPropertyUtils.getPropertyValue(expressionConfigurable, propertyName); 317 if (collectionValue == null) { 318 throw new RuntimeException("Property name: " 319 + propertyName 320 + " with collection type was not initialized. Cannot add expression result"); 321 } 322 collectionValue.add(propertyValue); 323 } else { 324 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, propertyValue); 325 } 326 } 327 } 328 329 protected void evaluatePropertyExpressions(View view, UifDictionaryBean expressionConfigurable, 330 Object contextObject, Map<String, Object> evaluationParameters) { 331 evaluatePropertyExpressions(view,expressionConfigurable,getContext(contextObject,evaluationParameters)); 332 } 333 334 /** 335 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#containsElPlaceholder(String) 336 */ 337 public boolean containsElPlaceholder(String value) { 338 boolean containsElPlaceholder = false; 339 340 if (StringUtils.isNotBlank(value)) { 341 String elPlaceholder = StringUtils.substringBetween(value, UifConstants.EL_PLACEHOLDER_PREFIX, 342 UifConstants.EL_PLACEHOLDER_SUFFIX); 343 if (StringUtils.isNotBlank(elPlaceholder)) { 344 containsElPlaceholder = true; 345 } 346 } 347 348 return containsElPlaceholder; 349 } 350 351 /** 352 * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#replaceBindingPrefixes(org.kuali.rice.krad.uif.view.View, 353 * Object, String) 354 */ 355 public String replaceBindingPrefixes(View view, Object object, String expression) { 356 String adjustedExpression = StringUtils.replace(expression, UifConstants.NO_BIND_ADJUST_PREFIX, ""); 357 358 // replace the field path prefix for DataFields 359 if (object instanceof DataField) { 360 361 // Get the binding path from the object 362 BindingInfo bindingInfo = ((DataField) object).getBindingInfo(); 363 String fieldPath = bindingInfo.getBindingPath(); 364 365 // Remove the property name from the binding path 366 fieldPath = StringUtils.removeEnd(fieldPath, "." + bindingInfo.getBindingName()); 367 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX, 368 fieldPath + "."); 369 } else { 370 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX, 371 ""); 372 } 373 374 // replace the default path prefix if there is one set on the view 375 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 376 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX, 377 view.getDefaultBindingObjectPath() + "."); 378 379 } else { 380 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX, 381 ""); 382 } 383 384 // replace line path binding prefix with the actual line path 385 if (adjustedExpression.contains(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) { 386 String linePath = getLinePathPrefixValue((Component) object); 387 388 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.LINE_PATH_BIND_ADJUST_PREFIX, 389 linePath + "."); 390 } 391 392 // replace node path binding prefix with the actual node path 393 if (adjustedExpression.contains(UifConstants.NODE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) { 394 String nodePath = ""; 395 396 Map<String, Object> context = ((Component) object).getContext(); 397 if (context.containsKey(UifConstants.ContextVariableNames.NODE_PATH)) { 398 nodePath = (String) context.get(UifConstants.ContextVariableNames.NODE_PATH); 399 } 400 401 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.NODE_PATH_BIND_ADJUST_PREFIX, 402 nodePath + "."); 403 } 404 405 return adjustedExpression; 406 } 407 408 /** 409 * Determines the value for the {@link org.kuali.rice.krad.uif.UifConstants#LINE_PATH_BIND_ADJUST_PREFIX} binding 410 * prefix 411 * based on collection group found in the component context 412 * 413 * @param component - component instance for which the prefix is configured on 414 * @return String line binding path or empty string if path not found 415 */ 416 protected static String getLinePathPrefixValue(Component component) { 417 String linePath = ""; 418 419 CollectionGroup collectionGroup = (CollectionGroup) (component.getContext().get( 420 UifConstants.ContextVariableNames.COLLECTION_GROUP)); 421 if (collectionGroup == null) { 422 LOG.warn("collection group not found for " + component + "," + component.getId() + ", " + component 423 .getComponentTypeName()); 424 return linePath; 425 } 426 427 Object indexObj = component.getContext().get(UifConstants.ContextVariableNames.INDEX); 428 if (indexObj != null) { 429 int index = (Integer) indexObj; 430 boolean addLine = false; 431 Object addLineObj = component.getContext().get(UifConstants.ContextVariableNames.IS_ADD_LINE); 432 433 if (addLineObj != null) { 434 addLine = (Boolean) addLineObj; 435 } 436 437 if (addLine) { 438 linePath = collectionGroup.getAddLineBindingInfo().getBindingPath(); 439 } else { 440 linePath = collectionGroup.getBindingInfo().getBindingPath() + "[" + index + "]"; 441 } 442 } 443 444 return linePath; 445 } 446 447 }