001/** 002 * Copyright 2005-2018 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 */ 016package org.kuali.rice.krad.uif.view; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.commons.lang.math.NumberUtils; 020import org.kuali.rice.core.api.exception.RiceRuntimeException; 021import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean; 022import org.kuali.rice.krad.uif.UifConstants; 023import org.kuali.rice.krad.uif.component.BindingInfo; 024import org.kuali.rice.krad.uif.component.Component; 025import org.kuali.rice.krad.uif.component.KeepExpression; 026import org.kuali.rice.krad.uif.component.PropertyReplacer; 027import org.kuali.rice.krad.uif.container.CollectionGroup; 028import org.kuali.rice.krad.uif.field.DataField; 029import org.kuali.rice.krad.uif.layout.LayoutManager; 030import org.kuali.rice.krad.uif.util.CopyUtils; 031import org.kuali.rice.krad.uif.util.ExpressionFunctions; 032import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 033import org.springframework.expression.Expression; 034import org.springframework.expression.ExpressionParser; 035import org.springframework.expression.common.TemplateParserContext; 036import org.springframework.expression.spel.standard.SpelExpressionParser; 037import org.springframework.expression.spel.support.StandardEvaluationContext; 038 039import java.lang.reflect.Method; 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.HashMap; 043import java.util.List; 044import java.util.Map; 045import java.util.regex.Matcher; 046import java.util.regex.Pattern; 047 048/** 049 * Evaluates expression language statements using the Spring EL engine 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053public class DefaultExpressionEvaluator implements ExpressionEvaluator { 054 055 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( 056 DefaultExpressionEvaluator.class); 057 058 protected static final Pattern SERVER_EVALUATION_PATTERN = Pattern.compile( 059 "(\\s?!?\\b(#|get|is)(.*?\\(.*?\\)))(\\s|$)"); 060 061 private StandardEvaluationContext evaluationContext; 062 063 private Map<String, Expression> cachedExpressions; 064 065 protected static ExpressionParser parser = new SpelExpressionParser(); 066 067 private static Method isAssignableFrom; 068 private static Method empty; 069 private static Method emptyList; 070 private static Method getService; 071 private static Method listContains; 072 private static Method getName; 073 private static Method getParam; 074 private static Method getParamAsBoolean; 075 private static Method getParamAsInteger; 076 private static Method getParamAsDouble; 077 private static Method hasPerm; 078 private static Method hasPermDtls; 079 private static Method hasPermTmpl; 080 private static Method sequence; 081 private static Method getDataObjectKey; 082 private static Method isProductionEnvironment; 083 084 static { 085 try { 086 isAssignableFrom = ExpressionFunctions.class.getDeclaredMethod("isAssignableFrom", 087 new Class[] {Class.class, Class.class}); 088 empty = ExpressionFunctions.class.getDeclaredMethod("empty", new Class[] {Object.class}); 089 emptyList = ExpressionFunctions.class.getDeclaredMethod("emptyList", new Class[] {List.class}); 090 listContains = ExpressionFunctions.class.getDeclaredMethod("listContains", 091 new Class[] {List.class, Object[].class}); 092 getName = ExpressionFunctions.class.getDeclaredMethod("getName", new Class[] {Class.class}); 093 getParam = ExpressionFunctions.class.getDeclaredMethod("getParam", 094 new Class[] {String.class, String.class, String.class}); 095 getParamAsBoolean = ExpressionFunctions.class.getDeclaredMethod("getParamAsBoolean", 096 new Class[] {String.class, String.class, String.class}); 097 getParamAsInteger = ExpressionFunctions.class.getDeclaredMethod("getParamAsInteger", 098 new Class[] {String.class, String.class, String.class}); 099 getParamAsDouble = ExpressionFunctions.class.getDeclaredMethod("getParamAsDouble", 100 new Class[] {String.class, String.class, String.class}); 101 hasPerm = ExpressionFunctions.class.getDeclaredMethod("hasPerm", new Class[] {String.class, String.class}); 102 hasPermDtls = ExpressionFunctions.class.getDeclaredMethod("hasPermDtls", 103 new Class[] {String.class, String.class, Map.class, Map.class}); 104 hasPermTmpl = ExpressionFunctions.class.getDeclaredMethod("hasPermTmpl", 105 new Class[] {String.class, String.class, Map.class, Map.class}); 106 getService = ExpressionFunctions.class.getDeclaredMethod("getService", new Class[] {String.class}); 107 sequence = ExpressionFunctions.class.getDeclaredMethod("sequence", new Class[] {String.class}); 108 getDataObjectKey = ExpressionFunctions.class.getDeclaredMethod("getDataObjectKey", 109 new Class[] {String.class}); 110 isProductionEnvironment = ExpressionFunctions.class.getDeclaredMethod("isProductionEnvironment", null); 111 } catch (NoSuchMethodException e) { 112 LOG.error("Custom function for el expressions not found: " + e.getMessage()); 113 throw new RuntimeException("Custom function for el expressions not found: " + e.getMessage(), e); 114 } 115 } 116 117 /** 118 * Default constructor 119 */ 120 public DefaultExpressionEvaluator() { 121 cachedExpressions = new HashMap<String, Expression>(); 122 } 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public void populatePropertyExpressionsFromGraph(UifDictionaryBean expressionConfigurable, 129 boolean buildRefreshGraphs) { 130 if (expressionConfigurable == null || expressionConfigurable.getExpressionGraph() == null) { 131 return; 132 } 133 134 Map<String, String> expressionGraph = expressionConfigurable.getExpressionGraph(); 135 for (Map.Entry<String, String> expressionEntry : expressionGraph.entrySet()) { 136 String propertyName = expressionEntry.getKey(); 137 String expression = expressionEntry.getValue(); 138 139 // by default assume expression belongs with passed in expressionConfigurable 140 UifDictionaryBean configurableWithExpression = expressionConfigurable; 141 142 // if property name is nested, we need to move the expression to the last expressionConfigurable 143 String adjustedPropertyName = propertyName; 144 if (StringUtils.contains(propertyName, ".")) { 145 String configurablePath = StringUtils.substringBeforeLast(propertyName, "."); 146 adjustedPropertyName = StringUtils.substringAfterLast(propertyName, "."); 147 148 Object nestedObject = ObjectPropertyUtils.getPropertyValue(expressionConfigurable, configurablePath); 149 if ((nestedObject == null) || !(nestedObject instanceof UifDictionaryBean)) { 150 throw new RiceRuntimeException( 151 "Object for which expression is configured on is null or does not implement UifDictionaryBean: '" 152 + configurablePath + "'"); 153 } 154 155 // use nested object as the expressionConfigurable which will get the property expression 156 configurableWithExpression = (UifDictionaryBean) nestedObject; 157 } 158 159 // only add the expression if it can be set on the configurable 160 if (ObjectPropertyUtils.isWritableProperty(configurableWithExpression, adjustedPropertyName)) { 161 configurableWithExpression.getPropertyExpressions().put(adjustedPropertyName, expression); 162 } 163 } 164 } 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public String parseExpression(String exp, List<String> controlNames, Map<String, Object> context) { 171 // clean up expression to ease parsing 172 exp = cleanUpExpression(exp); 173 174 // Evaluate server side method calls and constants, and place them in a map to be replaced later 175 Map<String, String> serverEvaluations = evaluateServerSideVariables(exp, context); 176 177 String conditionJs = exp; 178 controlNames.addAll(findControlNamesInExpression(exp)); 179 180 // Replace all known accepted strings with javascript equivalent 181 conditionJs = replaceWithJsEquivalents(conditionJs); 182 183 // Replace server evaluations in js string with evaluated value 184 for (String serverEvalToken : serverEvaluations.keySet()) { 185 String evaluatedValue = serverEvaluations.get(serverEvalToken); 186 conditionJs = conditionJs.replace(serverEvalToken, evaluatedValue); 187 } 188 189 List<String> removeControlNames = new ArrayList<String>(); 190 List<String> addControlNames = new ArrayList<String>(); 191 192 //convert property names to use coerceValue function and convert arrays to js arrays 193 for (String propertyName : controlNames) { 194 //array definitions are caught in controlNames because of the nature of the parse - convert them and remove 195 if (propertyName.trim().startsWith("{") && propertyName.trim().endsWith("}")) { 196 String array = propertyName.trim().replace('{', '['); 197 array = array.replace('}', ']'); 198 conditionJs = conditionJs.replace(propertyName, array); 199 removeControlNames.add(propertyName); 200 continue; 201 } 202 203 //handle not 204 if (propertyName.startsWith("!")) { 205 String actualPropertyName = StringUtils.removeStart(propertyName, "!"); 206 conditionJs = conditionJs.replace(propertyName, "!coerceValue(\"" + actualPropertyName + "\")"); 207 removeControlNames.add(propertyName); 208 addControlNames.add(actualPropertyName); 209 } else { 210 conditionJs = conditionJs.replace(propertyName, "coerceValue(\"" + propertyName + "\")"); 211 } 212 } 213 214 controlNames.removeAll(removeControlNames); 215 controlNames.addAll(addControlNames); 216 217 // Simple short circuit logic below 218 boolean complexCondition = conditionJs.contains(" (") || conditionJs.startsWith("("); 219 220 // Always remove AND'ed true 221 if (conditionJs.contains("true && ") || conditionJs.contains(" && true")) { 222 conditionJs = conditionJs.replace(" && true", ""); 223 conditionJs = conditionJs.replace("true && ", ""); 224 } 225 226 // An AND'ed false, or an OR'ed true, or true/false by themselves will always evaluate to the same outcome 227 // in a simple condition, so no need for client evaluation (server will handle the evaluation) 228 if (!complexCondition && (conditionJs.contains("false &&")) || conditionJs.contains("&& false") || conditionJs 229 .contains("|| true") || conditionJs.contains("true ||") || conditionJs.equals("true") || conditionJs 230 .equals("false")) { 231 conditionJs = ""; 232 } 233 234 return conditionJs; 235 } 236 237 /** 238 * Trim, remove expression tokens, and replace common symbols for consistency in parsing and output. 239 * 240 * @param exp the original expression 241 * @return the cleaned up expressiom 242 */ 243 private String cleanUpExpression(String exp) { 244 exp = exp.trim(); 245 if (exp.startsWith("@{")) { 246 exp = StringUtils.removeStart(exp, "@{"); 247 if (exp.endsWith("}")) { 248 exp = StringUtils.removeEnd(exp, "}"); 249 } 250 } 251 252 // Clean up the expression for parsing consistency 253 exp = StringUtils.replace(exp, "!=", " != "); 254 exp = StringUtils.replace(exp, "==", " == "); 255 exp = StringUtils.replace(exp, ">", " > "); 256 exp = StringUtils.replace(exp, "<", " < "); 257 exp = StringUtils.replace(exp, "<=", " <= "); 258 exp = StringUtils.replace(exp, ">=", " >= "); 259 exp = StringUtils.replace(exp, "&&", " && "); 260 exp = StringUtils.replace(exp, "||", " || "); 261 exp = StringUtils.replace(exp, " ", " "); 262 exp = StringUtils.replace(exp, " )", ")"); 263 exp = StringUtils.replace(exp, "( ", "("); 264 exp = StringUtils.replace(exp, " ,", ","); 265 266 return exp; 267 } 268 269 /** 270 * Evaluate server side variables and add them to a map with the key being the original var or call evaluated 271 * to be replaced later by the evaluated value. 272 * 273 * @param exp the expression to evaluate known server variables and methods 274 * @param context the expression evaluation context 275 * @return map of keys that are the original expression/variable, and the replacement value 276 */ 277 private Map<String, String> evaluateServerSideVariables(String exp, Map<String, Object> context) { 278 Map<String, String> serverEvaluations = new HashMap<String, String>(); 279 Matcher matcher = SERVER_EVALUATION_PATTERN.matcher(exp); 280 while (matcher.find()) { 281 String spelMethodCall = matcher.group(1); 282 283 Object value = this.evaluateExpression(context, spelMethodCall); 284 285 // Convert the value to expected js equivalent 286 if (value == null) { 287 serverEvaluations.put(spelMethodCall, "null"); 288 } else if (value instanceof String) { 289 serverEvaluations.put(spelMethodCall, "\"" + value + "\""); 290 } else if (value instanceof Boolean || NumberUtils.isNumber(value.toString())) { 291 serverEvaluations.put(spelMethodCall, value.toString()); 292 } else { 293 // Corner case, assume the object gives us something meaningful from toString, wrap in quotes 294 serverEvaluations.put(spelMethodCall, "\"" + value.toString() + "\""); 295 } 296 } 297 298 return serverEvaluations; 299 } 300 301 /** 302 * Replace springEL specific functionality with js equivalents. 303 * 304 * @param conditionJs the original expression 305 * @return the modified expression with js equivalent function calls 306 */ 307 private String replaceWithJsEquivalents(String conditionJs) { 308 conditionJs = conditionJs.replaceAll("\\s(?i:ne)\\s", " != "); 309 conditionJs = conditionJs.replaceAll("\\s(?i:eq)\\s", " == "); 310 conditionJs = conditionJs.replaceAll("\\s(?i:gt)\\s", " > "); 311 conditionJs = conditionJs.replaceAll("\\s(?i:lt)\\s", " < "); 312 conditionJs = conditionJs.replaceAll("\\s(?i:lte)\\s", " <= "); 313 conditionJs = conditionJs.replaceAll("\\s(?i:gte)\\s", " >= "); 314 conditionJs = conditionJs.replaceAll("\\s(?i:and)\\s", " && "); 315 conditionJs = conditionJs.replaceAll("\\s(?i:or)\\s", " || "); 316 conditionJs = conditionJs.replaceAll("\\s(?i:not)\\s", " != "); 317 conditionJs = conditionJs.replaceAll("\\s(?i:null)\\s?", " '' "); 318 conditionJs = conditionJs.replaceAll("\\s?(?i:#empty)\\((.*?)\\)", "isValueEmpty($1)"); 319 conditionJs = conditionJs.replaceAll("\\s?(?i:#listContains)\\((.*?)\\)", "listContains($1)"); 320 conditionJs = conditionJs.replaceAll("\\s?(?i:#emptyList)\\((.*?)\\)", "emptyList($1)"); 321 322 // Handle matches method conversion 323 if (conditionJs.contains("matches")) { 324 conditionJs = conditionJs.replaceAll("\\s+(?i:matches)\\s+'.*'", ".match(/" + "$0" + "/) != null "); 325 conditionJs = conditionJs.replaceAll("\\(/\\s+(?i:matches)\\s+'", "(/"); 326 conditionJs = conditionJs.replaceAll("'\\s*/\\)", "/)"); 327 } 328 329 return conditionJs; 330 } 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override 336 public List<String> findControlNamesInExpression(String exp) { 337 List<String> controlNames = new ArrayList<String>(); 338 String stack = ""; 339 340 boolean expectingSingleQuote = false; 341 boolean ignoreNext = false; 342 for (int i = 0; i < exp.length(); i++) { 343 char c = exp.charAt(i); 344 if (!expectingSingleQuote && !ignoreNext && (c == '(' || c == ' ' || c == ')')) { 345 evaluateCurrentStack(stack.trim(), controlNames); 346 //reset stack 347 stack = ""; 348 continue; 349 } else if (!ignoreNext && c == '\'') { 350 stack = stack + c; 351 expectingSingleQuote = !expectingSingleQuote; 352 } else if (c == '\\') { 353 stack = stack + c; 354 ignoreNext = !ignoreNext; 355 } else { 356 stack = stack + c; 357 ignoreNext = false; 358 } 359 } 360 361 if (StringUtils.isNotEmpty(stack)) { 362 evaluateCurrentStack(stack.trim(), controlNames); 363 } 364 365 return controlNames; 366 } 367 368 /** 369 * Used internally by parseExpression to evalute if the current stack is a property 370 * name (ie, will be a control on the form) 371 */ 372 protected void evaluateCurrentStack(String stack, List<String> controlNames) { 373 if (StringUtils.isBlank(stack)) { 374 return; 375 } 376 377 // These are special matches that can be directly replaced to a js equivalent (so skip evaluation of these) 378 if (!(stack.equals("==") || stack.equals("!=") || stack.equals(">") || stack.equals("<") || stack.equals(">=") 379 || stack.equals("<=") || stack.equalsIgnoreCase("ne") || stack.equalsIgnoreCase("eq") || stack 380 .equalsIgnoreCase("gt") || stack.equalsIgnoreCase("lt") || stack.equalsIgnoreCase("lte") || stack 381 .equalsIgnoreCase("gte") || stack.equalsIgnoreCase("matches") || stack.equalsIgnoreCase("null") || stack 382 .equalsIgnoreCase("false") || stack.equalsIgnoreCase("true") || stack.equalsIgnoreCase("and") || stack 383 .equalsIgnoreCase("or") || stack.startsWith("#") || stack.equals("!") || stack.startsWith("'") || stack 384 .endsWith("'"))) { 385 386 boolean isNumber = NumberUtils.isNumber(stack); 387 388 // If it is not a number must be check to see if it is a name of a control 389 if (!(isNumber)) { 390 //correct argument of a custom function ending in comma 391 if (StringUtils.endsWith(stack, ",")) { 392 stack = StringUtils.removeEnd(stack, ",").trim(); 393 } 394 395 if (!controlNames.contains(stack)) { 396 controlNames.add(stack); 397 } 398 } 399 } 400 } 401 402 /** 403 * {@inheritDoc} 404 */ 405 @Override 406 public void initializeEvaluationContext(Object contextObject) { 407 evaluationContext = new StandardEvaluationContext(contextObject); 408 409 addCustomFunctions(evaluationContext); 410 } 411 412 /** 413 * {@inheritDoc} 414 */ 415 @Override 416 public void evaluateExpressionsOnConfigurable(View view, UifDictionaryBean expressionConfigurable, 417 Map<String, Object> evaluationParameters) { 418 if ((expressionConfigurable instanceof Component) || (expressionConfigurable instanceof LayoutManager)) { 419 evaluatePropertyReplacers(view, expressionConfigurable, evaluationParameters); 420 } 421 evaluatePropertyExpressions(view, expressionConfigurable, evaluationParameters); 422 } 423 424 /** 425 * {@inheritDoc} 426 */ 427 @Override 428 public Object evaluateExpression(Map<String, Object> evaluationParameters, String expressionStr) { 429 Object result = null; 430 431 // if expression contains placeholders remove before evaluating 432 if (StringUtils.startsWith(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith( 433 expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX)) { 434 expressionStr = StringUtils.removeStart(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX); 435 expressionStr = StringUtils.removeEnd(expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX); 436 } 437 438 try { 439 Expression expression = retrieveCachedExpression(expressionStr); 440 441 if (evaluationParameters != null) { 442 evaluationContext.setVariables(evaluationParameters); 443 } 444 445 result = expression.getValue(evaluationContext); 446 } catch (Exception e) { 447 LOG.error("Exception evaluating expression: " + expressionStr); 448 throw new RuntimeException("Exception evaluating expression: " + expressionStr, e); 449 } 450 451 return result; 452 } 453 454 /** 455 * {@inheritDoc} 456 */ 457 @Override 458 public String evaluateExpressionTemplate(Map<String, Object> evaluationParameters, String expressionTemplate) { 459 String result = null; 460 461 try { 462 Expression expression = retrieveCachedExpression(expressionTemplate); 463 464 if (evaluationParameters != null) { 465 evaluationContext.setVariables(evaluationParameters); 466 } 467 468 result = expression.getValue(evaluationContext, String.class); 469 } catch (Exception e) { 470 LOG.error("Exception evaluating expression: " + expressionTemplate); 471 throw new RuntimeException("Exception evaluating expression: " + expressionTemplate, e); 472 } 473 474 return result; 475 } 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override 481 public void evaluatePropertyExpression(View view, Map<String, Object> evaluationParameters, 482 UifDictionaryBean expressionConfigurable, String propertyName, boolean removeExpression) { 483 484 Map<String, String> propertyExpressions = expressionConfigurable.getPropertyExpressions(); 485 if ((propertyExpressions == null) || !propertyExpressions.containsKey(propertyName)) { 486 return; 487 } 488 489 String expression = propertyExpressions.get(propertyName); 490 491 // If the property name is a default value which grabs a new sequence number don't evaluate the expression 492 // since a new sequence number has already been retrieved. 493 if (StringUtils.equals(propertyName, UifConstants.ComponentProperties.DEFAULT_VALUE) && 494 StringUtils.contains(expression, UifConstants.SEQUENCE_PREFIX)) { 495 return; 496 } 497 498 // check whether expression should be evaluated or property should retain the expression 499 if (CopyUtils.fieldHasAnnotation(expressionConfigurable.getClass(), propertyName, KeepExpression.class)) { 500 // set expression as property value to be handled by the component 501 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, expression); 502 return; 503 } 504 505 Object propertyValue = null; 506 507 // replace binding prefixes (lp, dp, fp) in expression before evaluation 508 String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression); 509 510 // determine whether the expression is a string template, or evaluates to another object type 511 if (StringUtils.startsWith(adjustedExpression, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith( 512 adjustedExpression, UifConstants.EL_PLACEHOLDER_SUFFIX) && (StringUtils.countMatches(adjustedExpression, 513 UifConstants.EL_PLACEHOLDER_PREFIX) == 1)) { 514 propertyValue = evaluateExpression(evaluationParameters, adjustedExpression); 515 } else { 516 // treat as string template 517 propertyValue = evaluateExpressionTemplate(evaluationParameters, adjustedExpression); 518 } 519 520 // if property name has the special indicator then we need to add the expression result to the property 521 // value instead of replace 522 if (StringUtils.endsWith(propertyName, ExpressionEvaluator.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR)) { 523 StringUtils.removeEnd(propertyName, ExpressionEvaluator.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR); 524 525 Collection collectionValue = ObjectPropertyUtils.getPropertyValue(expressionConfigurable, propertyName); 526 if (collectionValue == null) { 527 throw new RuntimeException("Property name: " + propertyName 528 + " with collection type was not initialized. Cannot add expression result"); 529 } 530 collectionValue.add(propertyValue); 531 } else { 532 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, propertyValue); 533 } 534 535 if (removeExpression) { 536 propertyExpressions.remove(propertyName); 537 } 538 } 539 540 /** 541 * {@inheritDoc} 542 */ 543 @Override 544 public boolean containsElPlaceholder(String value) { 545 boolean containsElPlaceholder = false; 546 547 if (StringUtils.isNotBlank(value)) { 548 String elPlaceholder = StringUtils.substringBetween(value, UifConstants.EL_PLACEHOLDER_PREFIX, 549 UifConstants.EL_PLACEHOLDER_SUFFIX); 550 if (StringUtils.isNotBlank(elPlaceholder)) { 551 containsElPlaceholder = true; 552 } 553 } 554 555 return containsElPlaceholder; 556 } 557 558 /** 559 * {@inheritDoc} 560 */ 561 @Override 562 public String replaceBindingPrefixes(View view, Object object, String expression) { 563 String adjustedExpression = StringUtils.replace(expression, UifConstants.NO_BIND_ADJUST_PREFIX, ""); 564 565 // replace the field path prefix for DataFields 566 if (StringUtils.contains(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX)) { 567 if (object instanceof DataField) { 568 // Get the binding path from the object 569 BindingInfo bindingInfo = ((DataField) object).getBindingInfo(); 570 571 Pattern pattern = Pattern.compile("(" + Pattern.quote(UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX) 572 + "[\\.\\w]+" + ")"); 573 Matcher matcher = pattern.matcher(adjustedExpression); 574 while (matcher.find()) { 575 String path = matcher.group(); 576 577 String adjustedPath = bindingInfo.getPropertyAdjustedBindingPath(path); 578 adjustedExpression = StringUtils.replace(adjustedExpression, path, adjustedPath); 579 } 580 } else { 581 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX, 582 ""); 583 } 584 } 585 586 // replace the default path prefix if there is one set on the view 587 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 588 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX, 589 view.getDefaultBindingObjectPath() + "."); 590 } else { 591 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX, 592 ""); 593 } 594 595 // replace line path binding prefix with the actual line path 596 if (adjustedExpression.contains(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) { 597 String linePath = getLinePathPrefixValue((Component) object); 598 599 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.LINE_PATH_BIND_ADJUST_PREFIX, 600 (StringUtils.isEmpty(linePath) ? linePath : linePath + ".")); 601 } 602 603 // replace node path binding prefix with the actual node path 604 if (adjustedExpression.contains(UifConstants.NODE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) { 605 String nodePath = ""; 606 607 Map<String, Object> context = ((Component) object).getContext(); 608 if (context != null && context.containsKey(UifConstants.ContextVariableNames.NODE_PATH)) { 609 nodePath = (String) context.get(UifConstants.ContextVariableNames.NODE_PATH); 610 } 611 612 adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.NODE_PATH_BIND_ADJUST_PREFIX, 613 nodePath + "."); 614 } 615 616 return adjustedExpression; 617 } 618 619 /** 620 * Attempts to retrieve the {@link Expression} instance for the given expression template, if 621 * not found one is created and added to the cache 622 * 623 * @param expressionTemplate template string for the expression 624 * @return Expression instance 625 */ 626 protected Expression retrieveCachedExpression(String expressionTemplate) { 627 Expression expression = null; 628 629 // return from the expression from cache if present 630 if (cachedExpressions.containsKey(expressionTemplate)) { 631 return cachedExpressions.get(expressionTemplate); 632 } 633 634 // not in cache, create the expression object 635 if (StringUtils.contains(expressionTemplate, UifConstants.EL_PLACEHOLDER_PREFIX)) { 636 expression = parser.parseExpression(expressionTemplate, new TemplateParserContext( 637 UifConstants.EL_PLACEHOLDER_PREFIX, UifConstants.EL_PLACEHOLDER_SUFFIX)); 638 } else { 639 expression = parser.parseExpression(expressionTemplate); 640 } 641 642 synchronized (cachedExpressions) { 643 cachedExpressions.put(expressionTemplate, expression); 644 } 645 646 return expression; 647 } 648 649 /** 650 * Registers custom functions for el expressions with the given context 651 * 652 * @param context - context instance to register functions to 653 */ 654 protected void addCustomFunctions(StandardEvaluationContext context) { 655 context.registerFunction("isAssignableFrom", isAssignableFrom); 656 context.registerFunction("empty", empty); 657 context.registerFunction("emptyList", emptyList); 658 context.registerFunction("getService", getService); 659 context.registerFunction("listContains", listContains); 660 context.registerFunction("getName", getName); 661 context.registerFunction("getParam", getParam); 662 context.registerFunction("getParamAsBoolean", getParamAsBoolean); 663 context.registerFunction("getParamAsInteger", getParamAsInteger); 664 context.registerFunction("getParamAsDouble", getParamAsDouble); 665 context.registerFunction("hasPerm", hasPerm); 666 context.registerFunction("hasPermDtls", hasPermDtls); 667 context.registerFunction("hasPermTmpl", hasPermTmpl); 668 context.registerFunction("sequence", sequence); 669 context.registerFunction("getDataObjectKey", getDataObjectKey); 670 context.registerFunction("isProductionEnvironment", isProductionEnvironment); 671 } 672 673 /** 674 * Iterates through any configured <code>PropertyReplacer</code> instances for the component and 675 * evaluates the given condition. If the condition is met, the replacement value is set on the 676 * corresponding property 677 * 678 * @param view - view instance being rendered 679 * @param expressionConfigurable - expressionConfigurable instance with property replacers list, 680 * should be either a component or layout manager 681 * @param evaluationParameters - parameters for el evaluation 682 */ 683 protected void evaluatePropertyReplacers(View view, UifDictionaryBean expressionConfigurable, 684 Map<String, Object> evaluationParameters) { 685 List<PropertyReplacer> replacers = null; 686 if (Component.class.isAssignableFrom(expressionConfigurable.getClass())) { 687 replacers = ((Component) expressionConfigurable).getPropertyReplacers(); 688 } else if (LayoutManager.class.isAssignableFrom(expressionConfigurable.getClass())) { 689 replacers = ((LayoutManager) expressionConfigurable).getPropertyReplacers(); 690 } 691 692 if (replacers != null) { 693 for (PropertyReplacer propertyReplacer : replacers) { 694 String expression = propertyReplacer.getCondition(); 695 String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression); 696 697 String conditionEvaluation = evaluateExpressionTemplate(evaluationParameters, adjustedExpression); 698 boolean conditionSuccess = Boolean.parseBoolean(conditionEvaluation); 699 if (conditionSuccess) { 700 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyReplacer.getPropertyName(), 701 propertyReplacer.getReplacement()); 702 } 703 } 704 } 705 } 706 707 /** 708 * Iterates through the keys of the property expressions map and invokes 709 * {@link #evaluatePropertyExpression(org.kuali.rice.krad.uif.view.View, java.util.Map, 710 * org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean, String, boolean)} 711 * 712 * <p> 713 * If the expression is an el template (part static text and part expression), only the 714 * expression part will be replaced with the result. More than one expressions may be contained 715 * within the template 716 * </p> 717 * 718 * @param view - view instance that is being rendered 719 * @param expressionConfigurable - object instance to evaluate expressions for 720 * @param evaluationParameters - map of additional parameters that may be used within the 721 * expressions 722 */ 723 protected void evaluatePropertyExpressions(View view, UifDictionaryBean expressionConfigurable, 724 Map<String, Object> evaluationParameters) { 725 if (expressionConfigurable == null) { 726 return; 727 } 728 729 Map<String, String> propertyExpressions = expressionConfigurable.getPropertyExpressions(); 730 if (propertyExpressions == null) { 731 return; 732 } 733 734 for (String propertyName : propertyExpressions.keySet()) { 735 evaluatePropertyExpression(view, evaluationParameters, expressionConfigurable, propertyName, false); 736 } 737 } 738 739 /** 740 * Determines the value for the 741 * {@link org.kuali.rice.krad.uif.UifConstants#LINE_PATH_BIND_ADJUST_PREFIX} binding prefix 742 * based on collection group found in the component context 743 * 744 * @param component - component instance for which the prefix is configured on 745 * @return String line binding path or empty string if path not found 746 */ 747 protected static String getLinePathPrefixValue(Component component) { 748 Map<String, Object> componentContext = component.getContext(); 749 if (componentContext == null) { 750 return ""; 751 } 752 753 CollectionGroup collectionGroup = (CollectionGroup) (componentContext.get( 754 UifConstants.ContextVariableNames.COLLECTION_GROUP)); 755 if (collectionGroup == null) { 756 LOG.warn("collection group not found for " + component + "," + component.getId() + ", " + component 757 .getComponentTypeName()); 758 return ""; 759 } 760 761 String linePath = ""; 762 763 Integer indexObj = (Integer) componentContext.get(UifConstants.ContextVariableNames.INDEX); 764 if (indexObj != null) { 765 int index = indexObj.intValue(); 766 767 boolean addLine = false; 768 Boolean addLineObj = (Boolean) componentContext.get(UifConstants.ContextVariableNames.IS_ADD_LINE); 769 770 if (addLineObj != null) { 771 addLine = addLineObj.booleanValue(); 772 } 773 774 if (addLine) { 775 linePath = collectionGroup.getAddLineBindingInfo().getBindingPath(); 776 } else { 777 linePath = collectionGroup.getBindingInfo().getBindingPath() + "[" + index + "]"; 778 } 779 } 780 781 return linePath; 782 } 783}