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