001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache 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 *      https://www.apache.org/licenses/LICENSE-2.0
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
017package org.springframework.web.servlet.tags;
018
019import java.io.IOException;
020
021import javax.servlet.jsp.JspException;
022import javax.servlet.jsp.PageContext;
023
024import org.springframework.context.expression.BeanFactoryResolver;
025import org.springframework.context.expression.EnvironmentAccessor;
026import org.springframework.context.expression.MapAccessor;
027import org.springframework.core.convert.ConversionService;
028import org.springframework.expression.AccessException;
029import org.springframework.expression.EvaluationContext;
030import org.springframework.expression.Expression;
031import org.springframework.expression.ExpressionParser;
032import org.springframework.expression.PropertyAccessor;
033import org.springframework.expression.TypedValue;
034import org.springframework.expression.spel.standard.SpelExpressionParser;
035import org.springframework.expression.spel.support.StandardEvaluationContext;
036import org.springframework.expression.spel.support.StandardTypeConverter;
037import org.springframework.lang.Nullable;
038import org.springframework.util.ObjectUtils;
039import org.springframework.web.util.JavaScriptUtils;
040import org.springframework.web.util.TagUtils;
041
042/**
043 * The {@code <eval>} tag evaluates a Spring expression (SpEL) and either prints
044 * the result or assigns it to a variable. Supports the standard JSP evaluation
045 * context consisting of implicit variables and scoped attributes.
046 *
047 * <table>
048 * <caption>Attribute Summary</caption>
049 * <thead>
050 * <tr>
051 * <th>Attribute</th>
052 * <th>Required?</th>
053 * <th>Runtime Expression?</th>
054 * <th>Description</th>
055 * </tr>
056 * </thead>
057 * <tbody>
058 * <tr>
059 * <td>expression</td>
060 * <td>true</td>
061 * <td>true</td>
062 * <td>The expression to evaluate.</td>
063 * </tr>
064 * <tr>
065 * <td>htmlEscape</td>
066 * <td>false</td>
067 * <td>true</td>
068 * <td>Set HTML escaping for this tag, as a boolean value.
069 * Overrides the default HTML escaping setting for the current page.</td>
070 * </tr>
071 * <tr>
072 * <td>javaScriptEscape</td>
073 * <td>false</td>
074 * <td>true</td>
075 * <td>Set JavaScript escaping for this tag, as a boolean value.
076 * Default is false.</td>
077 * </tr>
078 * <tr>
079 * <td>scope</td>
080 * <td>false</td>
081 * <td>true</td>
082 * <td>The scope for the var. 'application', 'session', 'request' and 'page'
083 * scopes are supported. Defaults to page scope. This attribute has no effect
084 * unless the var attribute is also defined.</td>
085 * </tr>
086 * <tr>
087 * <td>var</td>
088 * <td>false</td>
089 * <td>true</td>
090 * <td>The name of the variable to export the evaluation result to.
091 * If not specified the evaluation result is converted to a String and written
092 * as output.</td>
093 * </tr>
094 * </tbody>
095 * </table>
096 *
097 * @author Keith Donald
098 * @author Juergen Hoeller
099 * @since 3.0.1
100 */
101@SuppressWarnings("serial")
102public class EvalTag extends HtmlEscapingAwareTag {
103
104        /**
105         * {@link javax.servlet.jsp.PageContext} attribute for the
106         * page-level {@link EvaluationContext} instance.
107         */
108        private static final String EVALUATION_CONTEXT_PAGE_ATTRIBUTE =
109                        "org.springframework.web.servlet.tags.EVALUATION_CONTEXT";
110
111
112        private final ExpressionParser expressionParser = new SpelExpressionParser();
113
114        @Nullable
115        private Expression expression;
116
117        @Nullable
118        private String var;
119
120        private int scope = PageContext.PAGE_SCOPE;
121
122        private boolean javaScriptEscape = false;
123
124
125        /**
126         * Set the expression to evaluate.
127         */
128        public void setExpression(String expression) {
129                this.expression = this.expressionParser.parseExpression(expression);
130        }
131
132        /**
133         * Set the variable name to expose the evaluation result under.
134         * Defaults to rendering the result to the current JspWriter.
135         */
136        public void setVar(String var) {
137                this.var = var;
138        }
139
140        /**
141         * Set the scope to export the evaluation result to.
142         * This attribute has no meaning unless var is also defined.
143         */
144        public void setScope(String scope) {
145                this.scope = TagUtils.getScope(scope);
146        }
147
148        /**
149         * Set JavaScript escaping for this tag, as boolean value.
150         * Default is "false".
151         */
152        public void setJavaScriptEscape(boolean javaScriptEscape) throws JspException {
153                this.javaScriptEscape = javaScriptEscape;
154        }
155
156
157        @Override
158        public int doStartTagInternal() throws JspException {
159                return EVAL_BODY_INCLUDE;
160        }
161
162        @Override
163        public int doEndTag() throws JspException {
164                EvaluationContext evaluationContext =
165                                (EvaluationContext) this.pageContext.getAttribute(EVALUATION_CONTEXT_PAGE_ATTRIBUTE);
166                if (evaluationContext == null) {
167                        evaluationContext = createEvaluationContext(this.pageContext);
168                        this.pageContext.setAttribute(EVALUATION_CONTEXT_PAGE_ATTRIBUTE, evaluationContext);
169                }
170                if (this.var != null) {
171                        Object result = (this.expression != null ? this.expression.getValue(evaluationContext) : null);
172                        this.pageContext.setAttribute(this.var, result, this.scope);
173                }
174                else {
175                        try {
176                                String result = (this.expression != null ?
177                                                this.expression.getValue(evaluationContext, String.class) : null);
178                                result = ObjectUtils.getDisplayString(result);
179                                result = htmlEscape(result);
180                                result = (this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(result) : result);
181                                this.pageContext.getOut().print(result);
182                        }
183                        catch (IOException ex) {
184                                throw new JspException(ex);
185                        }
186                }
187                return EVAL_PAGE;
188        }
189
190        private EvaluationContext createEvaluationContext(PageContext pageContext) {
191                StandardEvaluationContext context = new StandardEvaluationContext();
192                context.addPropertyAccessor(new JspPropertyAccessor(pageContext));
193                context.addPropertyAccessor(new MapAccessor());
194                context.addPropertyAccessor(new EnvironmentAccessor());
195                context.setBeanResolver(new BeanFactoryResolver(getRequestContext().getWebApplicationContext()));
196                ConversionService conversionService = getConversionService(pageContext);
197                if (conversionService != null) {
198                        context.setTypeConverter(new StandardTypeConverter(conversionService));
199                }
200                return context;
201        }
202
203        @Nullable
204        private ConversionService getConversionService(PageContext pageContext) {
205                return (ConversionService) pageContext.getRequest().getAttribute(ConversionService.class.getName());
206        }
207
208
209        @SuppressWarnings("deprecation")
210        private static class JspPropertyAccessor implements PropertyAccessor {
211
212                private final PageContext pageContext;
213
214                @Nullable
215                private final javax.servlet.jsp.el.VariableResolver variableResolver;
216
217                public JspPropertyAccessor(PageContext pageContext) {
218                        this.pageContext = pageContext;
219                        this.variableResolver = pageContext.getVariableResolver();
220                }
221
222                @Override
223                @Nullable
224                public Class<?>[] getSpecificTargetClasses() {
225                        return null;
226                }
227
228                @Override
229                public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
230                        return (target == null &&
231                                        (resolveImplicitVariable(name) != null || this.pageContext.findAttribute(name) != null));
232                }
233
234                @Override
235                public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
236                        Object implicitVar = resolveImplicitVariable(name);
237                        if (implicitVar != null) {
238                                return new TypedValue(implicitVar);
239                        }
240                        return new TypedValue(this.pageContext.findAttribute(name));
241                }
242
243                @Override
244                public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) {
245                        return false;
246                }
247
248                @Override
249                public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) {
250                        throw new UnsupportedOperationException();
251                }
252
253                @Nullable
254                private Object resolveImplicitVariable(String name) throws AccessException {
255                        if (this.variableResolver == null) {
256                                return null;
257                        }
258                        try {
259                                return this.variableResolver.resolveVariable(name);
260                        }
261                        catch (Exception ex) {
262                                throw new AccessException(
263                                                "Unexpected exception occurred accessing '" + name + "' as an implicit variable", ex);
264                        }
265                }
266        }
267
268}