View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.service.impl;
17  
18  import net.sf.ehcache.CacheManager;
19  import net.sf.ehcache.Cache;
20  import net.sf.ehcache.Element;
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean;
23  import org.kuali.rice.krad.uif.UifConstants;
24  import org.kuali.rice.krad.uif.component.BindingInfo;
25  import org.kuali.rice.krad.uif.component.Component;
26  import org.kuali.rice.krad.uif.component.KeepExpression;
27  import org.kuali.rice.krad.uif.component.PropertyReplacer;
28  import org.kuali.rice.krad.uif.container.CollectionGroup;
29  import org.kuali.rice.krad.uif.field.DataField;
30  import org.kuali.rice.krad.uif.layout.LayoutManager;
31  import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
32  import org.kuali.rice.krad.uif.util.CloneUtils;
33  import org.kuali.rice.krad.uif.util.ExpressionFunctions;
34  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
35  import org.kuali.rice.krad.uif.view.View;
36  import org.springframework.expression.Expression;
37  import org.springframework.expression.ExpressionParser;
38  import org.springframework.expression.common.TemplateParserContext;
39  import org.springframework.expression.spel.standard.SpelExpressionParser;
40  import org.springframework.expression.spel.support.StandardEvaluationContext;
41  
42  import java.lang.reflect.Method;
43  import java.util.Collection;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Map.Entry;
47  
48  /**
49   * Evaluates expression language statements using the Spring EL engine
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public class ExpressionEvaluatorServiceImpl implements ExpressionEvaluatorService {
54      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
55              ExpressionEvaluatorServiceImpl.class);
56  
57      protected static ExpressionParser parser = new SpelExpressionParser();
58  
59      static{
60          CacheManager singletonManager = CacheManager.create();
61          singletonManager.addCache(new Cache("ExpressionEvaluatorServiceCache", 1500, false, false, 120, 0));
62      }
63  
64      Cache expressionCache = CacheManager.getInstance().getCache("ExpressionEvaluatorServiceCache");
65  
66      private static Method isAssignableFrom;
67      private static Method empty;
68      private static Method emptyList;
69      private static Method listContains;
70      private static Method getName;
71      private static Method getParm;
72      private static Method getParmInd;
73      private static Method hasPerm;
74      private static Method hasPermDtls;
75      private static Method hasPermTmpl;
76      private static Method sequence;
77  
78      static{
79          try{
80              isAssignableFrom = ExpressionFunctions.class.getDeclaredMethod("isAssignableFrom", new Class[]{Class.class, Class.class});
81              empty = ExpressionFunctions.class.getDeclaredMethod("empty", new Class[]{Object.class});
82              emptyList = ExpressionFunctions.class.getDeclaredMethod("emptyList", new Class[]{List.class});
83              listContains = ExpressionFunctions.class.getDeclaredMethod("listContains", new Class[]{List.class, Object[].class});
84              getName = ExpressionFunctions.class.getDeclaredMethod("getName", new Class[]{Class.class});
85              getParm = ExpressionFunctions.class.getDeclaredMethod("getParm", new Class[]{String.class, String.class, String.class});
86              getParmInd = ExpressionFunctions.class.getDeclaredMethod("getParmInd", new Class[]{String.class, String.class, String.class});
87              hasPerm = ExpressionFunctions.class.getDeclaredMethod("hasPerm", new Class[]{String.class, String.class});
88              hasPermDtls = ExpressionFunctions.class.getDeclaredMethod("hasPermDtls", new Class[]{String.class, String.class, Map.class, Map.class});
89              hasPermTmpl = ExpressionFunctions.class.getDeclaredMethod("hasPermTmpl", new Class[]{String.class, String.class, Map.class, Map.class});
90              sequence = ExpressionFunctions.class.getDeclaredMethod("sequence", new Class[]{String.class});
91          }catch(NoSuchMethodException e){
92              LOG.error("Custom function for el expressions not found: " + e.getMessage());
93              throw new RuntimeException("Custom function for el expressions not found: " + e.getMessage(), e);
94          }
95      }
96  
97      /**
98       * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionsOnConfigurable(org.kuali.rice.krad.uif.view.View,
99       *      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 }