001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.web.util;
018:
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import javax.servlet.ServletContext;
024: import javax.servlet.jsp.JspException;
025: import javax.servlet.jsp.PageContext;
026: import javax.servlet.jsp.el.ELException;
027: import javax.servlet.jsp.el.Expression;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
032:
033: import org.springframework.util.Assert;
034: import org.springframework.util.ClassUtils;
035:
036: /**
037: * Convenience methods for transparent access to JSP 2.0's built-in
038: * {@link javax.servlet.jsp.el.ExpressionEvaluator} or the standalone
039: * {@link org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager}
040: * of Jakarta's JSTL implementation.
041: *
042: * <p>Automatically detects JSP 2.0 or Jakarta JSTL, preferring the JSP 2.0
043: * mechanism if available. Also detects the JSP 2.0 API being present but
044: * the runtime JSP engine not actually supporting JSP 2.0, falling back to
045: * the Jakarta JSTL in this case. Throws an exception when encountering actual
046: * EL expressions if neither JSP 2.0 nor the Jakarta JSTL is available.
047: *
048: * <p>In the case of JSP 2.0, this class will by default use standard
049: * <code>evaluate</code> calls. If your application server happens to be
050: * inefficient in that respect, consider setting Spring's "cacheJspExpressions"
051: * context-param in <code>web.xml</code> to "true", which will use
052: * <code>parseExpression</code> calls with cached Expression objects instead.
053: *
054: * <p>The evaluation methods check if the value contains "${" before
055: * invoking the EL evaluator, treating the value as "normal" expression
056: * (i.e. a literal String value) else.
057: *
058: * <p>Note: The evaluation methods do not have a runtime dependency on
059: * JSP 2.0 or on Jakarta's JSTL implementation, as long as they are not
060: * asked to process actual EL expressions. This allows for using EL-aware
061: * tags with Java-based JSP expressions instead, for example.
062: *
063: * @author Juergen Hoeller
064: * @author Alef Arendsen
065: * @since 11.07.2003
066: * @see javax.servlet.jsp.el.ExpressionEvaluator#evaluate
067: * @see javax.servlet.jsp.el.ExpressionEvaluator#parseExpression
068: * @see org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager
069: */
070: public abstract class ExpressionEvaluationUtils {
071:
072: /**
073: * JSP 2.0 expression cache parameter at the servlet context level
074: * (i.e. a context-param in <code>web.xml</code>): "cacheJspExpressions".
075: */
076: public static final String EXPRESSION_CACHE_CONTEXT_PARAM = "cacheJspExpressions";
077:
078: public static final String EXPRESSION_PREFIX = "${";
079:
080: public static final String EXPRESSION_SUFFIX = "}";
081:
082: private static final String EXPRESSION_CACHE_FLAG_CONTEXT_ATTR = ExpressionEvaluationUtils.class
083: .getName()
084: + ".CACHE_JSP_EXPRESSIONS";
085:
086: private static final String EXPRESSION_CACHE_MAP_CONTEXT_ATTR = ExpressionEvaluationUtils.class
087: .getName()
088: + ".JSP_EXPRESSION_CACHE";
089:
090: private static final String JSP_20_CLASS_NAME = "javax.servlet.jsp.el.ExpressionEvaluator";
091:
092: private static final String JAKARTA_JSTL_CLASS_NAME = "org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager";
093:
094: private static final Log logger = LogFactory
095: .getLog(ExpressionEvaluationUtils.class);
096:
097: private static ExpressionEvaluationHelper helper;
098:
099: static {
100: ClassLoader cl = ExpressionEvaluationUtils.class
101: .getClassLoader();
102: if (ClassUtils.isPresent(JSP_20_CLASS_NAME, cl)) {
103: logger.debug("Found JSP 2.0 ExpressionEvaluator");
104: if (ClassUtils.isPresent(JAKARTA_JSTL_CLASS_NAME, cl)) {
105: logger
106: .debug("Found Jakarta JSTL ExpressionEvaluatorManager");
107: helper = new Jsp20ExpressionEvaluationHelper(
108: new JakartaExpressionEvaluationHelper());
109: } else {
110: helper = new Jsp20ExpressionEvaluationHelper(
111: new NoExpressionEvaluationHelper());
112: }
113: } else if (ClassUtils.isPresent(JAKARTA_JSTL_CLASS_NAME, cl)) {
114: logger
115: .debug("Found Jakarta JSTL ExpressionEvaluatorManager");
116: helper = new JakartaExpressionEvaluationHelper();
117: } else {
118: logger.debug("JSP expression evaluation not available");
119: helper = new NoExpressionEvaluationHelper();
120: }
121: }
122:
123: /**
124: * Check if the given expression value is an EL expression.
125: * @param value the expression to check
126: * @return <code>true</code> if the expression is an EL expression,
127: * <code>false</code> otherwise
128: */
129: public static boolean isExpressionLanguage(String value) {
130: return (value != null && value.indexOf(EXPRESSION_PREFIX) != -1);
131: }
132:
133: /**
134: * Evaluate the given expression (be it EL or a literal String value)
135: * to an Object of a given type,
136: * @param attrName name of the attribute (typically a JSP tag attribute)
137: * @param attrValue value of the attribute
138: * @param resultClass class that the result should have (String, Integer, Boolean)
139: * @param pageContext current JSP PageContext
140: * @return the result of the evaluation
141: * @throws JspException in case of parsing errors, also in case of type mismatch
142: * if the passed-in literal value is not an EL expression and not assignable to
143: * the result class
144: */
145: public static Object evaluate(String attrName, String attrValue,
146: Class resultClass, PageContext pageContext)
147: throws JspException {
148:
149: if (isExpressionLanguage(attrValue)) {
150: return doEvaluate(attrName, attrValue, resultClass,
151: pageContext);
152: } else if (attrValue != null && resultClass != null
153: && !resultClass.isInstance(attrValue)) {
154: throw new JspException("Attribute value \"" + attrValue
155: + "\" is neither a JSP EL expression nor "
156: + "assignable to result class ["
157: + resultClass.getName() + "]");
158: } else {
159: return attrValue;
160: }
161: }
162:
163: /**
164: * Evaluate the given expression (be it EL or a literal String value) to an Object.
165: * @param attrName name of the attribute (typically a JSP tag attribute)
166: * @param attrValue value of the attribute
167: * @param pageContext current JSP PageContext
168: * @return the result of the evaluation
169: * @throws JspException in case of parsing errors
170: */
171: public static Object evaluate(String attrName, String attrValue,
172: PageContext pageContext) throws JspException {
173:
174: if (isExpressionLanguage(attrValue)) {
175: return doEvaluate(attrName, attrValue, Object.class,
176: pageContext);
177: } else {
178: return attrValue;
179: }
180: }
181:
182: /**
183: * Evaluate the given expression (be it EL or a literal String value) to a String.
184: * @param attrName name of the attribute (typically a JSP tag attribute)
185: * @param attrValue value of the attribute
186: * @param pageContext current JSP PageContext
187: * @return the result of the evaluation
188: * @throws JspException in case of parsing errors
189: */
190: public static String evaluateString(String attrName,
191: String attrValue, PageContext pageContext)
192: throws JspException {
193:
194: if (isExpressionLanguage(attrValue)) {
195: return (String) doEvaluate(attrName, attrValue,
196: String.class, pageContext);
197: } else {
198: return attrValue;
199: }
200: }
201:
202: /**
203: * Evaluate the given expression (be it EL or a literal String value) to an integer.
204: * @param attrName name of the attribute (typically a JSP tag attribute)
205: * @param attrValue value of the attribute
206: * @param pageContext current JSP PageContext
207: * @return the result of the evaluation
208: * @throws JspException in case of parsing errors
209: */
210: public static int evaluateInteger(String attrName,
211: String attrValue, PageContext pageContext)
212: throws JspException {
213:
214: if (isExpressionLanguage(attrValue)) {
215: return ((Integer) doEvaluate(attrName, attrValue,
216: Integer.class, pageContext)).intValue();
217: } else {
218: return Integer.parseInt(attrValue);
219: }
220: }
221:
222: /**
223: * Evaluate the given expression (be it EL or a literal String value) to a boolean.
224: * @param attrName name of the attribute (typically a JSP tag attribute)
225: * @param attrValue value of the attribute
226: * @param pageContext current JSP PageContext
227: * @return the result of the evaluation
228: * @throws JspException in case of parsing errors
229: */
230: public static boolean evaluateBoolean(String attrName,
231: String attrValue, PageContext pageContext)
232: throws JspException {
233:
234: if (isExpressionLanguage(attrValue)) {
235: return ((Boolean) doEvaluate(attrName, attrValue,
236: Boolean.class, pageContext)).booleanValue();
237: } else {
238: return Boolean.valueOf(attrValue).booleanValue();
239: }
240: }
241:
242: /**
243: * Actually evaluate the given expression (be it EL or a literal String value)
244: * to an Object of a given type. Supports concatenated expressions,
245: * for example: "${var1}text${var2}"
246: * @param attrName name of the attribute
247: * @param attrValue value of the attribute
248: * @param resultClass class that the result should have
249: * @param pageContext current JSP PageContext
250: * @return the result of the evaluation
251: * @throws JspException in case of parsing errors
252: */
253: private static Object doEvaluate(String attrName, String attrValue,
254: Class resultClass, PageContext pageContext)
255: throws JspException {
256:
257: Assert.notNull(attrValue, "Attribute value must not be null");
258: Assert.notNull(resultClass, "Result class must not be null");
259: Assert.notNull(pageContext, "PageContext must not be null");
260:
261: if (resultClass.isAssignableFrom(String.class)) {
262: StringBuffer resultValue = null;
263: int exprPrefixIndex = -1;
264: int exprSuffixIndex = 0;
265: do {
266: exprPrefixIndex = attrValue.indexOf(EXPRESSION_PREFIX,
267: exprSuffixIndex);
268: if (exprPrefixIndex != -1) {
269: int prevExprSuffixIndex = exprSuffixIndex;
270: exprSuffixIndex = attrValue.indexOf(
271: EXPRESSION_SUFFIX, exprPrefixIndex
272: + EXPRESSION_PREFIX.length());
273: String expr = null;
274: if (exprSuffixIndex != -1) {
275: exprSuffixIndex += EXPRESSION_SUFFIX.length();
276: expr = attrValue.substring(exprPrefixIndex,
277: exprSuffixIndex);
278: } else {
279: expr = attrValue.substring(exprPrefixIndex);
280: }
281: if (expr.length() == attrValue.length()) {
282: // A single expression without static prefix or suffix ->
283: // parse it with the specified result class rather than String.
284: return helper.evaluate(attrName, attrValue,
285: resultClass, pageContext);
286: } else {
287: // We actually need to concatenate partial expressions into a String.
288: if (resultValue == null) {
289: resultValue = new StringBuffer();
290: }
291: resultValue.append(attrValue.substring(
292: prevExprSuffixIndex, exprPrefixIndex));
293: resultValue.append(helper.evaluate(attrName,
294: expr, String.class, pageContext));
295: }
296: } else {
297: if (resultValue == null) {
298: resultValue = new StringBuffer();
299: }
300: resultValue.append(attrValue
301: .substring(exprSuffixIndex));
302: }
303: } while (exprPrefixIndex != -1 && exprSuffixIndex != -1);
304: return resultValue.toString();
305: }
306:
307: else {
308: return helper.evaluate(attrName, attrValue, resultClass,
309: pageContext);
310: }
311: }
312:
313: /**
314: * Determine whether JSP 2.0 expressions are supposed to be cached
315: * and return the corresponding cache Map, or <code>null</code> if
316: * caching is not enabled.
317: * @param pageContext current JSP PageContext
318: * @return the cache Map, or <code>null</code> if caching is disabled
319: */
320: private static Map getJspExpressionCache(PageContext pageContext) {
321: ServletContext servletContext = pageContext.getServletContext();
322: Map cacheMap = (Map) servletContext
323: .getAttribute(EXPRESSION_CACHE_MAP_CONTEXT_ATTR);
324: if (cacheMap == null) {
325: Boolean cacheFlag = (Boolean) servletContext
326: .getAttribute(EXPRESSION_CACHE_FLAG_CONTEXT_ATTR);
327: if (cacheFlag == null) {
328: cacheFlag = Boolean
329: .valueOf(servletContext
330: .getInitParameter(EXPRESSION_CACHE_CONTEXT_PARAM));
331: servletContext.setAttribute(
332: EXPRESSION_CACHE_FLAG_CONTEXT_ATTR, cacheFlag);
333: }
334: if (cacheFlag.booleanValue()) {
335: cacheMap = Collections.synchronizedMap(new HashMap());
336: servletContext.setAttribute(
337: EXPRESSION_CACHE_MAP_CONTEXT_ATTR, cacheMap);
338: }
339: }
340: return cacheMap;
341: }
342:
343: /**
344: * Internal interface for evaluating a JSP EL expression.
345: */
346: private static interface ExpressionEvaluationHelper {
347:
348: public Object evaluate(String attrName, String attrValue,
349: Class resultClass, PageContext pageContext)
350: throws JspException;
351: }
352:
353: /**
354: * Fallback ExpressionEvaluationHelper:
355: * always throws an exception in case of an actual EL expression.
356: */
357: private static class NoExpressionEvaluationHelper implements
358: ExpressionEvaluationHelper {
359:
360: public Object evaluate(String attrName, String attrValue,
361: Class resultClass, PageContext pageContext)
362: throws JspException {
363:
364: throw new JspException(
365: "Neither JSP 2.0 nor Jakarta JSTL available - cannot parse JSP EL expression \""
366: + attrValue + "\"");
367: }
368: }
369:
370: /**
371: * Actual invocation of the Jakarta ExpressionEvaluatorManager.
372: * In separate inner class to avoid runtime dependency on Jakarta's
373: * JSTL implementation, for evaluation of non-EL expressions.
374: */
375: private static class JakartaExpressionEvaluationHelper implements
376: ExpressionEvaluationHelper {
377:
378: public Object evaluate(String attrName, String attrValue,
379: Class resultClass, PageContext pageContext)
380: throws JspException {
381:
382: return ExpressionEvaluatorManager.evaluate(attrName,
383: attrValue, resultClass, pageContext);
384: }
385: }
386:
387: /**
388: * Actual invocation of the JSP 2.0 ExpressionEvaluator.
389: * In separate inner class to avoid runtime dependency on JSP 2.0,
390: * for evaluation of non-EL expressions.
391: */
392: private static class Jsp20ExpressionEvaluationHelper implements
393: ExpressionEvaluationHelper {
394:
395: private final ExpressionEvaluationHelper fallback;
396:
397: private boolean fallbackNecessary = false;
398:
399: public Jsp20ExpressionEvaluationHelper(
400: ExpressionEvaluationHelper fallback) {
401: this .fallback = fallback;
402: }
403:
404: public Object evaluate(String attrName, String attrValue,
405: Class resultClass, PageContext pageContext)
406: throws JspException {
407:
408: if (isFallbackNecessary()) {
409: return this .fallback.evaluate(attrName, attrValue,
410: resultClass, pageContext);
411: }
412:
413: try {
414: Map expressionCache = getJspExpressionCache(pageContext);
415: if (expressionCache != null) {
416: // We are supposed to explicitly create and cache JSP Expression objects.
417: ExpressionCacheKey cacheKey = new ExpressionCacheKey(
418: attrValue, resultClass);
419: Expression expr = (Expression) expressionCache
420: .get(cacheKey);
421: if (expr == null) {
422: expr = pageContext.getExpressionEvaluator()
423: .parseExpression(attrValue,
424: resultClass, null);
425: expressionCache.put(cacheKey, expr);
426: }
427: return expr.evaluate(pageContext
428: .getVariableResolver());
429: } else {
430: // We're simply calling the JSP 2.0 evaluate method straight away.
431: return pageContext.getExpressionEvaluator()
432: .evaluate(attrValue, resultClass,
433: pageContext.getVariableResolver(),
434: null);
435: }
436: } catch (ELException ex) {
437: throw new JspException(
438: "Parsing of JSP EL expression \"" + attrValue
439: + "\" failed", ex);
440: } catch (LinkageError err) {
441: logger
442: .debug(
443: "JSP 2.0 ExpressionEvaluator API present but not implemented - using fallback",
444: err);
445: setFallbackNecessary();
446: return this .fallback.evaluate(attrName, attrValue,
447: resultClass, pageContext);
448: }
449: }
450:
451: private synchronized boolean isFallbackNecessary() {
452: return this .fallbackNecessary;
453: }
454:
455: private synchronized void setFallbackNecessary() {
456: this .fallbackNecessary = true;
457: }
458: }
459:
460: /**
461: * Cache key class for JSP 2.0 Expression objects.
462: */
463: private static class ExpressionCacheKey {
464:
465: private final String value;
466: private final Class resultClass;
467: private final int hashCode;
468:
469: public ExpressionCacheKey(String value, Class resultClass) {
470: this .value = value;
471: this .resultClass = resultClass;
472: this .hashCode = this .value.hashCode() * 29
473: + this .resultClass.hashCode();
474: }
475:
476: public boolean equals(Object obj) {
477: if (!(obj instanceof ExpressionCacheKey)) {
478: return false;
479: }
480: ExpressionCacheKey other = (ExpressionCacheKey) obj;
481: return (this .value.equals(other.value) && this .resultClass
482: .equals(other.resultClass));
483: }
484:
485: public int hashCode() {
486: return this.hashCode;
487: }
488: }
489:
490: }
|