/* * ============================================================================= * * Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ============================================================================= */ package org.thymeleaf.standard.expression; import java.util.Collections; import java.util.Map; import ognl.OgnlContext; import ognl.OgnlException; import ognl.OgnlRuntime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.IContext; import org.thymeleaf.context.IExpressionContext; import org.thymeleaf.context.ITemplateContext; import org.thymeleaf.exceptions.TemplateProcessingException; import org.thymeleaf.expression.IExpressionObjects; import org.thymeleaf.standard.util.StandardExpressionUtils; /** * <p> * Evaluator for variable expressions (<tt>${...}</tt>) in Thymeleaf Standard Expressions, using the * OGNL expression language. * </p> * <p> * Note a class with this name existed since 2.0.9, but it was completely reimplemented * in Thymeleaf 3.0 * </p> * * @author Daniel Fernández * * @since 3.0.0 * */ public final class OGNLVariableExpressionEvaluator implements IStandardVariableExpressionEvaluator { private static final Logger logger = LoggerFactory.getLogger(OGNLVariableExpressionEvaluator.class); private static final String EXPRESSION_CACHE_TYPE_OGNL = "ognl"; private static Map<String,Object> CONTEXT_VARIABLES_MAP_NOEXPOBJECTS_RESTRICTIONS = (Map<String,Object>) (Map<?,?>)Collections.singletonMap( OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS, OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS); private final boolean applyOGNLShortcuts; public OGNLVariableExpressionEvaluator(final boolean applyOGNLShortcuts) { super(); this.applyOGNLShortcuts = applyOGNLShortcuts; /* * INITIALIZE AND REGISTER THE PROPERTY ACCESSOR */ final OGNLContextPropertyAccessor accessor = new OGNLContextPropertyAccessor(); OgnlRuntime.setPropertyAccessor(IContext.class, accessor); } public final Object evaluate( final IExpressionContext context, final IStandardVariableExpression expression, final StandardExpressionExecutionContext expContext) { return evaluate(context, expression, expContext, this.applyOGNLShortcuts); } private static Object evaluate( final IExpressionContext context, final IStandardVariableExpression expression, final StandardExpressionExecutionContext expContext, final boolean applyOGNLShortcuts) { try { if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] OGNL expression: evaluating expression \"{}\" on target", TemplateEngine.threadIndex(), expression.getExpression()); } final IEngineConfiguration configuration = context.getConfiguration(); final String exp = expression.getExpression(); final boolean useSelectionAsRoot = expression.getUseSelectionAsRoot(); if (exp == null) { throw new TemplateProcessingException("Expression content is null, which is not allowed"); } final ComputedOGNLExpression parsedExpression = obtainComputedOGNLExpression(configuration, expression, exp, applyOGNLShortcuts); final Map<String,Object> contextVariablesMap; if (parsedExpression.mightNeedExpressionObjects) { // The IExpressionObjects implementation returned by processing contexts that include the Standard // Dialects will be lazy in the creation of expression objects (i.e. they won't be created until really // needed). And in order for this behaviour to be accepted by OGNL, we will be wrapping this object // inside an implementation of Map<String,Object>, which will afterwards be fed to the constructor // of an OgnlContext object. // Note this will never happen with shortcut expressions, as the '#' character with which all // expression object names start is not allowed by the OGNLShortcutExpression parser. final IExpressionObjects expressionObjects = context.getExpressionObjects(); contextVariablesMap = new OGNLExpressionObjectsWrapper(expressionObjects); // We might need to apply restrictions on the request parameters. In the case of OGNL, the only way we // can actually communicate with the PropertyAccessor, (OGNLVariablesMapPropertyAccessor), which is the // agent in charge of applying such restrictions, is by adding a context variable that the property accessor // can later lookup during evaluation. if (expContext.getRestrictVariableAccess()) { contextVariablesMap.put(OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS, OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS); } else { contextVariablesMap.remove(OGNLContextPropertyAccessor.RESTRICT_REQUEST_PARAMETERS); } } else { if (expContext.getRestrictVariableAccess()) { contextVariablesMap = CONTEXT_VARIABLES_MAP_NOEXPOBJECTS_RESTRICTIONS; } else { contextVariablesMap = Collections.EMPTY_MAP; } } // The root object on which we will evaluate expressions will depend on whether a selection target is // active or not... final ITemplateContext templateContext = (context instanceof ITemplateContext ? (ITemplateContext) context : null); final Object evaluationRoot = (useSelectionAsRoot && templateContext != null && templateContext.hasSelectionTarget()? templateContext.getSelectionTarget() : templateContext); // Execute the expression! final Object result; try { result = executeExpression(configuration, parsedExpression.expression, contextVariablesMap, evaluationRoot); } catch (final OGNLShortcutExpression.OGNLShortcutExpressionNotApplicableException notApplicable) { // We tried to apply shortcuts, but it is not possible for this expression even if it parsed OK, // so we need to empty the cache and try again disabling shortcuts. Once processed for the first time, // an OGNL (non-shortcut) parsed expression will already be cached and this exception will not be // thrown again invalidateComputedOGNLExpression(configuration, expression, exp); return evaluate(context, expression, expContext, false); } if (!expContext.getPerformTypeConversion()) { return result; } final IStandardConversionService conversionService = StandardExpressions.getConversionService(configuration); return conversionService.convert(context, result, String.class); } catch (final Exception e) { throw new TemplateProcessingException( "Exception evaluating OGNL expression: \"" + expression.getExpression() + "\"", e); } } private static ComputedOGNLExpression obtainComputedOGNLExpression( final IEngineConfiguration configuration, final IStandardVariableExpression expression, final String exp, final boolean applyOGNLShortcuts) throws OgnlException { if (expression instanceof VariableExpression) { final VariableExpression vexpression = (VariableExpression) expression; Object cachedExpression = vexpression.getCachedExpression(); if (cachedExpression != null && cachedExpression instanceof ComputedOGNLExpression) { return (ComputedOGNLExpression) cachedExpression; } cachedExpression = parseComputedOGNLExpression(configuration, exp, applyOGNLShortcuts); if (cachedExpression != null) { vexpression.setCachedExpression(cachedExpression); } return (ComputedOGNLExpression) cachedExpression; } if (expression instanceof SelectionVariableExpression) { final SelectionVariableExpression vexpression = (SelectionVariableExpression) expression; Object cachedExpression = vexpression.getCachedExpression(); if (cachedExpression != null && cachedExpression instanceof ComputedOGNLExpression) { return (ComputedOGNLExpression) cachedExpression; } cachedExpression = parseComputedOGNLExpression(configuration, exp, applyOGNLShortcuts); if (cachedExpression != null) { vexpression.setCachedExpression(cachedExpression); } return (ComputedOGNLExpression) cachedExpression; } return parseComputedOGNLExpression(configuration, exp, applyOGNLShortcuts); } private static ComputedOGNLExpression parseComputedOGNLExpression( final IEngineConfiguration configuration, final String exp, final boolean applyOGNLShortcuts) throws OgnlException { ComputedOGNLExpression parsedExpression = (ComputedOGNLExpression) ExpressionCache.getFromCache(configuration, exp, EXPRESSION_CACHE_TYPE_OGNL); if (parsedExpression != null) { return parsedExpression; } // The result of parsing might be an OGNL expression AST or a ShortcutOGNLExpression (for simple cases) parsedExpression = parseExpression(exp, applyOGNLShortcuts); ExpressionCache.putIntoCache(configuration, exp, parsedExpression, EXPRESSION_CACHE_TYPE_OGNL); return parsedExpression; } private static void invalidateComputedOGNLExpression( final IEngineConfiguration configuration, final IStandardVariableExpression expression, final String exp) { if (expression instanceof VariableExpression) { final VariableExpression vexpression = (VariableExpression) expression; vexpression.setCachedExpression(null); } else if (expression instanceof SelectionVariableExpression) { final SelectionVariableExpression vexpression = (SelectionVariableExpression) expression; vexpression.setCachedExpression(null); } ExpressionCache.removeFromCache(configuration, exp, EXPRESSION_CACHE_TYPE_OGNL); } @Override public String toString() { return "OGNL"; } private static ComputedOGNLExpression parseExpression(final String expression, final boolean applyOGNLShortcuts) throws OgnlException { final boolean mightNeedExpressionObjects = StandardExpressionUtils.mightNeedExpressionObjects(expression); if (applyOGNLShortcuts) { final String[] parsedExpression = OGNLShortcutExpression.parse(expression); if (parsedExpression != null) { return new ComputedOGNLExpression(new OGNLShortcutExpression(parsedExpression), mightNeedExpressionObjects); } } return new ComputedOGNLExpression(ognl.Ognl.parseExpression(expression), mightNeedExpressionObjects); } private static Object executeExpression( final IEngineConfiguration configuration, final Object parsedExpression, final Map<String,Object> context, final Object root) throws Exception { if (parsedExpression instanceof OGNLShortcutExpression) { return ((OGNLShortcutExpression) parsedExpression).evaluate(configuration, context, root); } // We create the OgnlContext here instead of just sending the Map as context because that prevents OGNL from // creating the OgnlContext empty and then setting the context Map variables one by one final OgnlContext ognlContext = new OgnlContext(context); return ognl.Ognl.getValue(parsedExpression, ognlContext, root); } private static final class ComputedOGNLExpression { final Object expression; final boolean mightNeedExpressionObjects; ComputedOGNLExpression(final Object expression, final boolean mightNeedExpressionObjects) { super(); this.expression = expression; this.mightNeedExpressionObjects = mightNeedExpressionObjects; } } }