001/**
002 * Copyright 2005-2016 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.service.impl;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Map.Entry;
022
023import org.apache.commons.lang.StringUtils;
024import org.kuali.rice.krad.uif.UifConstants;
025import org.kuali.rice.krad.uif.component.Component;
026import org.kuali.rice.krad.uif.component.Configurable;
027import org.kuali.rice.krad.uif.component.KeepExpression;
028import org.kuali.rice.krad.uif.component.PropertyReplacer;
029import org.kuali.rice.krad.uif.layout.LayoutManager;
030import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
031import org.kuali.rice.krad.uif.util.CloneUtils;
032import org.kuali.rice.krad.uif.util.ExpressionFunctions;
033import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
034import org.springframework.expression.Expression;
035import org.springframework.expression.ExpressionParser;
036import org.springframework.expression.common.TemplateParserContext;
037import org.springframework.expression.spel.standard.SpelExpressionParser;
038import org.springframework.expression.spel.support.StandardEvaluationContext;
039
040/**
041 * Evaluates expression language statements using the Spring EL engine TODO:
042 * Look into using Rice KRMS for evaluation
043 *
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046public class ExpressionEvaluatorServiceImpl implements ExpressionEvaluatorService {
047    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
048            ExpressionEvaluatorServiceImpl.class);
049
050    /**
051     * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateObjectExpressions(java.lang.Object,
052     *      java.lang.Object, java.util.Map)
053     */
054    public void evaluateObjectExpressions(Object object, Object contextObject,
055            Map<String, Object> evaluationParameters) {
056        if ((object instanceof Component) || (object instanceof LayoutManager)) {
057            evaluatePropertyReplacers(object, contextObject, evaluationParameters);
058        }
059        evaluatePropertyExpressions(object, contextObject, evaluationParameters);
060    }
061
062    /**
063     * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpressionTemplate(java.lang.Object,
064     *      java.util.Map, java.lang.String)
065     */
066    public String evaluateExpressionTemplate(Object contextObject, Map<String, Object> evaluationParameters,
067            String expressionTemplate) {
068        StandardEvaluationContext context = new StandardEvaluationContext(contextObject);
069        context.setVariables(evaluationParameters);
070        addCustomFunctions(context);
071
072        ExpressionParser parser = new SpelExpressionParser();
073
074        String result = null;
075        try {
076            Expression expression = null;
077            if (StringUtils.contains(expressionTemplate, UifConstants.EL_PLACEHOLDER_PREFIX)) {
078                expression = parser.parseExpression(expressionTemplate, new TemplateParserContext(
079                        UifConstants.EL_PLACEHOLDER_PREFIX, UifConstants.EL_PLACEHOLDER_SUFFIX));
080            } else {
081                expression = parser.parseExpression(expressionTemplate);
082            }
083
084            result = expression.getValue(context, String.class);
085        } catch (Exception e) {
086            LOG.error("Exception evaluating expression: " + expressionTemplate);
087            throw new RuntimeException("Exception evaluating expression: " + expressionTemplate, e);
088        }
089
090        return result;
091    }
092
093    /**
094     * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#evaluateExpression(java.lang.Object,
095     *      java.util.Map, java.lang.String)
096     */
097    public Object evaluateExpression(Object contextObject, Map<String, Object> evaluationParameters,
098            String expressionStr) {
099        StandardEvaluationContext context = new StandardEvaluationContext(contextObject);
100        context.setVariables(evaluationParameters);
101        addCustomFunctions(context);
102
103        // if expression contains placeholders remove before evaluating
104        if (StringUtils.startsWith(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith(
105                expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX)) {
106            expressionStr = StringUtils.removeStart(expressionStr, UifConstants.EL_PLACEHOLDER_PREFIX);
107            expressionStr = StringUtils.removeEnd(expressionStr, UifConstants.EL_PLACEHOLDER_SUFFIX);
108        }
109
110        ExpressionParser parser = new SpelExpressionParser();
111        Object result = null;
112        try {
113            Expression expression = parser.parseExpression(expressionStr);
114
115            result = expression.getValue(context);
116        } catch (Exception e) {
117            LOG.error("Exception evaluating expression: " + expressionStr);
118            throw new RuntimeException("Exception evaluating expression: " + expressionStr, e);
119        }
120
121        return result;
122    }
123
124    /**
125     * Registers custom functions for el expressions with the given context
126     *
127     * @param context - context instance to register functions to
128     */
129    protected void addCustomFunctions(StandardEvaluationContext context) {
130        try {
131            // TODO: possibly reflect ExpressionFunctions and add automatically
132            context.registerFunction("isAssignableFrom", ExpressionFunctions.class.getDeclaredMethod("isAssignableFrom",
133                    new Class[]{Class.class, Class.class}));
134            context.registerFunction("empty", ExpressionFunctions.class.getDeclaredMethod("empty",
135                    new Class[]{Object.class}));
136            context.registerFunction("getName", ExpressionFunctions.class.getDeclaredMethod("getName",
137                    new Class[]{Class.class}));
138            context.registerFunction("getParm", ExpressionFunctions.class.getDeclaredMethod("getParm",
139                    new Class[]{String.class, String.class, String.class}));
140            context.registerFunction("getParmInd", ExpressionFunctions.class.getDeclaredMethod("getParmInd",
141                    new Class[]{String.class, String.class, String.class}));
142            context.registerFunction("hasPerm", ExpressionFunctions.class.getDeclaredMethod("hasPerm",
143                    new Class[]{String.class, String.class}));
144            context.registerFunction("hasPermDtls", ExpressionFunctions.class.getDeclaredMethod("hasPermDtls",
145                    new Class[]{String.class, String.class, Map.class, Map.class}));
146            context.registerFunction("hasPermTmpl", ExpressionFunctions.class.getDeclaredMethod("hasPermTmpl",
147                    new Class[]{String.class, String.class, Map.class, Map.class}));
148        } catch (NoSuchMethodException e) {
149            LOG.error("Custom function for el expressions not found: " + e.getMessage());
150            throw new RuntimeException("Custom function for el expressions not found: " + e.getMessage(), e);
151        }
152    }
153
154    /**
155     * Iterates through any configured <code>PropertyReplacer</code> instances for the component and
156     * evaluates the given condition. If the condition is met, the replacement value is set on the
157     * corresponding property
158     *
159     * @param object - object instance with property replacers list, should be either a component or layout manager
160     * @param contextObject - context for el evaluation
161     * @param evaluationParameters - parameters for el evaluation
162     */
163    protected void evaluatePropertyReplacers(Object object, Object contextObject,
164            Map<String, Object> evaluationParameters) {
165        List<PropertyReplacer> replacers = null;
166        if (Component.class.isAssignableFrom(object.getClass())) {
167            replacers = ((Component) object).getPropertyReplacers();
168        } else if (LayoutManager.class.isAssignableFrom(object.getClass())) {
169            replacers = ((LayoutManager) object).getPropertyReplacers();
170        }
171
172        for (PropertyReplacer propertyReplacer : replacers) {
173            String conditionEvaluation = evaluateExpressionTemplate(contextObject, evaluationParameters,
174                    propertyReplacer.getCondition());
175            boolean conditionSuccess = Boolean.parseBoolean(conditionEvaluation);
176            if (conditionSuccess) {
177                ObjectPropertyUtils.setPropertyValue(object, propertyReplacer.getPropertyName(),
178                        propertyReplacer.getReplacement());
179            }
180        }
181    }
182
183    /**
184     * Retrieves the Map from the given object that containing the property expressions that should
185     * be evaluated. Each expression is then evaluated and the result is used to set the property value
186     *
187     * <p>
188     * If the expression is an el template (part static text and part expression), only the expression
189     * part will be replaced with the result. More than one expressions may be contained within the template
190     * </p>
191     *
192     * @param object - object instance to evaluate expressions for
193     * @param contextObject - object providing the default context for expressions
194     * @param evaluationParameters - map of additional parameters that may be used within the expressions
195     */
196    protected void evaluatePropertyExpressions(Object object, Object contextObject,
197            Map<String, Object> evaluationParameters) {
198        Map<String, String> propertyExpressions = new HashMap<String, String>();
199        if (Configurable.class.isAssignableFrom(object.getClass())) {
200            propertyExpressions = ((Configurable) object).getPropertyExpressions();
201        }
202
203        for (Entry<String, String> propertyExpression : propertyExpressions.entrySet()) {
204            String propertyName = propertyExpression.getKey();
205            String expression = propertyExpression.getValue();
206
207            // check whether expression should be evaluated or property should retain the expression
208            if (CloneUtils.fieldHasAnnotation(object.getClass(), propertyName, KeepExpression.class)) {
209                // set expression as property value to be handled by the component
210                ObjectPropertyUtils.setPropertyValue(object, propertyName, expression);
211                continue;
212            }
213
214            Object propertyValue = null;
215
216            // determine whether the expression is a string template, or evaluates to another object type
217            if (StringUtils.startsWith(expression, UifConstants.EL_PLACEHOLDER_PREFIX) && StringUtils.endsWith(
218                    expression, UifConstants.EL_PLACEHOLDER_SUFFIX) && (StringUtils.countMatches(expression,
219                    UifConstants.EL_PLACEHOLDER_PREFIX) == 1)) {
220                propertyValue = evaluateExpression(contextObject, evaluationParameters, expression);
221            } else {
222                // treat as string template
223                propertyValue = evaluateExpressionTemplate(contextObject, evaluationParameters, expression);
224            }
225
226            ObjectPropertyUtils.setPropertyValue(object, propertyName, propertyValue);
227        }
228    }
229
230    /**
231     * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService#containsElPlaceholder(java.lang.String)
232     */
233    public boolean containsElPlaceholder(String value) {
234        boolean containsElPlaceholder = false;
235
236        if (StringUtils.isNotBlank(value)) {
237            String elPlaceholder = StringUtils.substringBetween(value, UifConstants.EL_PLACEHOLDER_PREFIX,
238                    UifConstants.EL_PLACEHOLDER_SUFFIX);
239            if (StringUtils.isNotBlank(elPlaceholder)) {
240                containsElPlaceholder = true;
241            }
242        }
243
244        return containsElPlaceholder;
245    }
246
247}