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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.component.BindingInfo;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.component.KeepExpression;
24  import org.kuali.rice.krad.uif.component.PropertyReplacer;
25  import org.kuali.rice.krad.uif.container.CollectionGroup;
26  import org.kuali.rice.krad.uif.field.DataField;
27  import org.kuali.rice.krad.uif.layout.LayoutManager;
28  import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
29  import org.kuali.rice.krad.uif.util.CloneUtils;
30  import org.kuali.rice.krad.uif.util.ExpressionFunctions;
31  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
32  import org.kuali.rice.krad.uif.view.View;
33  import org.springframework.expression.Expression;
34  import org.springframework.expression.ExpressionParser;
35  import org.springframework.expression.common.TemplateParserContext;
36  import org.springframework.expression.spel.standard.SpelExpressionParser;
37  import org.springframework.expression.spel.support.StandardEvaluationContext;
38  
39  import java.util.Collection;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Map.Entry;
43  
44  /**
45   * Evaluates expression language statements using the Spring EL engine
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public class ExpressionEvaluatorServiceImpl implements ExpressionEvaluatorService {
50      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
51              ExpressionEvaluatorServiceImpl.class);
52  
53      /**
54       * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionsOnConfigurable(org.kuali.rice.krad.uif.view.View,
55       *      org.kuali.rice.krad.datadictionary.uif.UifDictionaryBean, java.lang.Object,
56       *      java.util.Map<java.lang.String,java.lang.Object>)
57       */
58      public void evaluateExpressionsOnConfigurable(View view, UifDictionaryBean expressionConfigurable,
59              Object contextObject, Map<String, Object> evaluationParameters) {
60          if ((expressionConfigurable instanceof Component) || (expressionConfigurable instanceof LayoutManager)) {
61              evaluatePropertyReplacers(view, expressionConfigurable, contextObject, evaluationParameters);
62          }
63          evaluatePropertyExpressions(view, expressionConfigurable, contextObject, evaluationParameters);
64      }
65  
66      /**
67       * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionTemplate(java.lang.Object,
68       *      java.util.Map, java.lang.String)
69       */
70      public String evaluateExpressionTemplate(Object contextObject, Map<String, Object> evaluationParameters,
71              String expressionTemplate) {
72          StandardEvaluationContext context = new StandardEvaluationContext(contextObject);
73          context.setVariables(evaluationParameters);
74          addCustomFunctions(context);
75  
76          ExpressionParser parser = new SpelExpressionParser();
77  
78          String result = null;
79          try {
80              Expression expression = null;
81              if (StringUtils.contains(expressionTemplate, UifConstants.EL_PLACEHOLDER_PREFIX)) {
82                  expression = parser.parseExpression(expressionTemplate, new TemplateParserContext(
83                          UifConstants.EL_PLACEHOLDER_PREFIX, UifConstants.EL_PLACEHOLDER_SUFFIX));
84              } else {
85                  expression = parser.parseExpression(expressionTemplate);
86              }
87  
88              result = expression.getValue(context, String.class);
89          } catch (Exception e) {
90              LOG.error("Exception evaluating expression: " + expressionTemplate);
91              throw new RuntimeException("Exception evaluating expression: " + expressionTemplate, e);
92          }
93  
94          return result;
95      }
96  
97      /**
98       * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpression(java.lang.Object,
99       *      java.util.Map, java.lang.String)
100      */
101     public Object evaluateExpression(Object contextObject, Map<String, Object> evaluationParameters,
102             String expressionStr) {
103         StandardEvaluationContext context = new StandardEvaluationContext(contextObject);
104         context.setVariables(evaluationParameters);
105         addCustomFunctions(context);
106 
107         // if expression contains placeholders remove before evaluating
108         if (StringUtils.startsWith(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith(
109                 expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX)) {
110             expressionStr = StringUtils.removeStart(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX);
111             expressionStr = StringUtils.removeEnd(expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX);
112         }
113 
114         ExpressionParser parser = new SpelExpressionParser();
115         Object result = null;
116         try {
117             Expression expression = parser.parseExpression(expressionStr);
118 
119             result = expression.getValue(context);
120         } catch (Exception e) {
121             LOG.error("Exception evaluating expression: " + expressionStr);
122             throw new RuntimeException("Exception evaluating expression: " + expressionStr, e);
123         }
124 
125         return result;
126     }
127 
128     /**
129      * Registers custom functions for el expressions with the given context
130      *
131      * @param context - context instance to register functions to
132      */
133     protected void addCustomFunctions(StandardEvaluationContext context) {
134         try {
135             // TODO: possibly reflect ExpressionFunctions and add automatically
136             context.registerFunction("isAssignableFrom", ExpressionFunctions.class.getDeclaredMethod("isAssignableFrom",
137                     new Class[]{Class.class, Class.class}));
138             context.registerFunction("empty", ExpressionFunctions.class.getDeclaredMethod("empty",
139                     new Class[]{Object.class}));
140             context.registerFunction("emptyList", ExpressionFunctions.class.getDeclaredMethod("emptyList",
141                     new Class[]{List.class}));
142             context.registerFunction("listContains", ExpressionFunctions.class.getDeclaredMethod("listContains",
143                     new Class[]{List.class, Object[].class}));
144             context.registerFunction("getName", ExpressionFunctions.class.getDeclaredMethod("getName",
145                     new Class[]{Class.class}));
146             context.registerFunction("getParm", ExpressionFunctions.class.getDeclaredMethod("getParm",
147                     new Class[]{String.class, String.class, String.class}));
148             context.registerFunction("getParmInd", ExpressionFunctions.class.getDeclaredMethod("getParmInd",
149                     new Class[]{String.class, String.class, String.class}));
150             context.registerFunction("hasPerm", ExpressionFunctions.class.getDeclaredMethod("hasPerm",
151                     new Class[]{String.class, String.class}));
152             context.registerFunction("hasPermDtls", ExpressionFunctions.class.getDeclaredMethod("hasPermDtls",
153                     new Class[]{String.class, String.class, Map.class, Map.class}));
154             context.registerFunction("hasPermTmpl", ExpressionFunctions.class.getDeclaredMethod("hasPermTmpl",
155                     new Class[]{String.class, String.class, Map.class, Map.class}));
156             context.registerFunction("sequence", ExpressionFunctions.class.getDeclaredMethod("sequence",
157                     new Class[]{String.class}));
158         } catch (NoSuchMethodException e) {
159             LOG.error("Custom function for el expressions not found: " + e.getMessage());
160             throw new RuntimeException("Custom function for el expressions not found: " + e.getMessage(), e);
161         }
162     }
163 
164     /**
165      * Iterates through any configured <code>PropertyReplacer</code> instances for the component and
166      * evaluates the given condition. If the condition is met, the replacement value is set on the
167      * corresponding property
168      *
169      * @param view - view instance being rendered
170      * @param expressionConfigurable - expressionConfigurable instance with property replacers list, should be either a
171      * component or layout
172      * manager
173      * @param contextObject - context for el evaluation
174      * @param evaluationParameters - parameters for el evaluation
175      */
176     protected void evaluatePropertyReplacers(View view, UifDictionaryBean expressionConfigurable, Object contextObject,
177             Map<String, Object> evaluationParameters) {
178         List<PropertyReplacer> replacers = null;
179         if (Component.class.isAssignableFrom(expressionConfigurable.getClass())) {
180             replacers = ((Component) expressionConfigurable).getPropertyReplacers();
181         } else if (LayoutManager.class.isAssignableFrom(expressionConfigurable.getClass())) {
182             replacers = ((LayoutManager) expressionConfigurable).getPropertyReplacers();
183         }
184 
185         for (PropertyReplacer propertyReplacer : replacers) {
186             String expression = propertyReplacer.getCondition();
187             String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression);
188 
189             String conditionEvaluation = evaluateExpressionTemplate(contextObject, evaluationParameters,
190                     adjustedExpression);
191             boolean conditionSuccess = Boolean.parseBoolean(conditionEvaluation);
192             if (conditionSuccess) {
193                 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyReplacer.getPropertyName(),
194                         propertyReplacer.getReplacement());
195             }
196         }
197     }
198 
199     /**
200      * Retrieves the Map from the given object that containing the property expressions that should
201      * be evaluated. Each expression is then evaluated and the result is used to set the property value
202      *
203      * <p>
204      * If the expression is an el template (part static text and part expression), only the expression
205      * part will be replaced with the result. More than one expressions may be contained within the template
206      * </p>
207      *
208      * @param view - view instance that is being rendered
209      * @param expressionConfigurable - object instance to evaluate expressions for
210      * @param contextObject - object providing the default context for expressions
211      * @param evaluationParameters - map of additional parameters that may be used within the expressions
212      */
213     protected void evaluatePropertyExpressions(View view, UifDictionaryBean expressionConfigurable,
214             Object contextObject, Map<String, Object> evaluationParameters) {
215         Map<String, String> propertyExpressions = expressionConfigurable.getPropertyExpressions();
216         for (Entry<String, String> propertyExpression : propertyExpressions.entrySet()) {
217             String propertyName = propertyExpression.getKey();
218             String expression = propertyExpression.getValue();
219 
220             // check whether expression should be evaluated or property should retain the expression
221             if (CloneUtils.fieldHasAnnotation(expressionConfigurable.getClass(), propertyName, KeepExpression.class)) {
222                 // set expression as property value to be handled by the component
223                 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, expression);
224                 continue;
225             }
226 
227             Object propertyValue = null;
228 
229             // replace binding prefixes (lp, dp, fp) in expression before evaluation
230             String adjustedExpression = replaceBindingPrefixes(view, expressionConfigurable, expression);
231 
232             // determine whether the expression is a string template, or evaluates to another object type
233             if (StringUtils.startsWith(adjustedExpression, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith(
234                     adjustedExpression, UifConstants.EL_PLACEHOLDER_SUFFIX) && (StringUtils.countMatches(
235                     adjustedExpression, UifConstants.EL_PLACEHOLDER_PREFIX) == 1)) {
236                 propertyValue = evaluateExpression(contextObject, evaluationParameters, adjustedExpression);
237             } else {
238                 // treat as string template
239                 propertyValue = evaluateExpressionTemplate(contextObject, evaluationParameters, adjustedExpression);
240             }
241 
242             // if property name has the special indicator then we need to add the expression result to the property
243             // value instead of replace
244             if (StringUtils.endsWith(propertyName, ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR)) {
245                 StringUtils.removeEnd(propertyName, ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR);
246 
247                 Collection collectionValue = ObjectPropertyUtils.getPropertyValue(expressionConfigurable, propertyName);
248                 if (collectionValue == null) {
249                     throw new RuntimeException("Property name: "
250                             + propertyName
251                             + " with collection type was not initialized. Cannot add expression result");
252                 }
253                 collectionValue.add(propertyValue);
254             } else {
255                 ObjectPropertyUtils.setPropertyValue(expressionConfigurable, propertyName, propertyValue);
256             }
257         }
258     }
259 
260     /**
261      * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#containsElPlaceholder(java.lang.String)
262      */
263     public boolean containsElPlaceholder(String value) {
264         boolean containsElPlaceholder = false;
265 
266         if (StringUtils.isNotBlank(value)) {
267             String elPlaceholder = StringUtils.substringBetween(value, UifConstants.EL_PLACEHOLDER_PREFIX,
268                     UifConstants.EL_PLACEHOLDER_SUFFIX);
269             if (StringUtils.isNotBlank(elPlaceholder)) {
270                 containsElPlaceholder = true;
271             }
272         }
273 
274         return containsElPlaceholder;
275     }
276 
277     /**
278      * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#replaceBindingPrefixes(org.kuali.rice.krad.uif.view.View,
279      *      java.lang.Object, java.lang.String)
280      */
281     public String replaceBindingPrefixes(View view, Object object, String expression) {
282         String adjustedExpression = StringUtils.replace(expression, UifConstants.NO_BIND_ADJUST_PREFIX, "");
283 
284         // replace the field path prefix for DataFields
285         if (object instanceof DataField) {
286 
287             // Get the binding path from the object
288             BindingInfo bindingInfo = ((DataField) object).getBindingInfo();
289             String fieldPath = bindingInfo.getBindingPath();
290 
291             // Remove the property name from the binding path
292             fieldPath = StringUtils.removeEnd(fieldPath, "." + bindingInfo.getBindingName());
293             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX,
294                     fieldPath + ".");
295         } else {
296             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX,
297                     "");
298         }
299 
300         // replace the default path prefix if there is one set on the view
301         if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
302             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX,
303                     view.getDefaultBindingObjectPath() + ".");
304 
305         } else {
306             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX,
307                     "");
308         }
309 
310         // replace line path binding prefix with the actual line path
311         if (adjustedExpression.contains(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) {
312             String linePath = getLinePathPrefixValue((Component) object);
313 
314             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.LINE_PATH_BIND_ADJUST_PREFIX,
315                     linePath + ".");
316         }
317 
318         // replace node path binding prefix with the actual node path
319         if (adjustedExpression.contains(UifConstants.NODE_PATH_BIND_ADJUST_PREFIX) && (object instanceof Component)) {
320             String nodePath = "";
321 
322             Map<String, Object> context = ((Component) object).getContext();
323             if (context.containsKey(UifConstants.ContextVariableNames.NODE_PATH)) {
324                 nodePath = (String) context.get(UifConstants.ContextVariableNames.NODE_PATH);
325             }
326 
327             adjustedExpression = StringUtils.replace(adjustedExpression, UifConstants.NODE_PATH_BIND_ADJUST_PREFIX,
328                     nodePath + ".");
329         }
330 
331         return adjustedExpression;
332     }
333 
334     /**
335      * Determines the value for the {@link org.kuali.rice.krad.uif.UifConstants#LINE_PATH_BIND_ADJUST_PREFIX} binding
336      * prefix
337      * based on collection group found in the component context
338      *
339      * @param component - component instance for which the prefix is configured on
340      * @return String line binding path or empty string if path not found
341      */
342     protected static String getLinePathPrefixValue(Component component) {
343         String linePath = "";
344 
345         CollectionGroup collectionGroup = (CollectionGroup) (component.getContext().get(
346                 UifConstants.ContextVariableNames.COLLECTION_GROUP));
347         if (collectionGroup == null) {
348             LOG.warn("collection group not found for " + component + "," + component.getId() + ", " + component
349                     .getComponentTypeName());
350             return linePath;
351         }
352 
353         Object indexObj = component.getContext().get(UifConstants.ContextVariableNames.INDEX);
354         if (indexObj != null) {
355             int index = (Integer) indexObj;
356             boolean addLine = false;
357             Object addLineObj = component.getContext().get(UifConstants.ContextVariableNames.IS_ADD_LINE);
358 
359             if (addLineObj != null) {
360                 addLine = (Boolean) addLineObj;
361             }
362 
363             if (addLine) {
364                 linePath = collectionGroup.getAddLineBindingInfo().getBindingPath();
365             } else {
366                 linePath = collectionGroup.getBindingInfo().getBindingPath() + "[" + index + "]";
367             }
368         }
369 
370         return linePath;
371     }
372 
373 }