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