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}