57434 - Fix race condition in ELEvaluator for 1.0 expressions
diff --git a/CHANGES.txt b/CHANGES.txt
index 58385ca..abcbf0f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 Changes in 1.2.6 release
 60950 JSTL TransformSupport XSL import not finding relative path
+57434 Race condition in EL1.0 validation
 
 Changes in 1.2.5 release
 - Set version identifiers correctly, no other changes.
diff --git a/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/ELEvaluator.java b/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/ELEvaluator.java
index 821c921..7876908 100644
--- a/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/ELEvaluator.java
+++ b/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/ELEvaluator.java
@@ -102,13 +102,13 @@
      * growth.
      * <p>NOTE: use LinkedHashmap if a dependency on J2SE 1.4+ is ok
      */
-    static Map sCachedExpressionStrings = null;
+    private static Map sCachedExpressionStrings = null;
 
     /**
      * The mapping from ExpectedType to Maps mapping literal String to
      * parsed value *
      */
-    static Map sCachedExpectedTypes = new HashMap();
+    private static final Map sCachedExpectedTypes = new HashMap();
 
     /**
      * The static Logger *
@@ -123,11 +123,12 @@
     /**
      * Flag if the cache should be bypassed *
      */
-    boolean mBypassCache;
+    private volatile boolean mBypassCache;
 
     /**
      * The PageContext *
      */
+    // TODO: Find a better way to override the expression cache size that does not need this field.
     PageContext pageContext;
 
 
@@ -256,40 +257,46 @@
             return "";
         }
 
-        if (!(mBypassCache) && (sCachedExpressionStrings == null)) {
-            createExpressionStringMap();
+        if (mBypassCache) {
+            return parseExpressionUncached(pExpressionString);
         }
 
+        Map cache = getOrCreateExpressionStringMap(pageContext);
+
         // See if it's in the cache
-        Object ret =
-                mBypassCache ?
-                        null :
-                        sCachedExpressionStrings.get(pExpressionString);
+        Object ret = cache.get(pExpressionString);
+        if (ret != null) {
+            return ret;
+        }
 
-        if (ret == null) {
-            // Parse the expression
+        ret = parseExpressionUncached(pExpressionString);
+        cache.put(pExpressionString, ret);
+        return ret;
+    }
+
+    /**
+     * Parse an expression string bypassing the cache.
+     *
+     * This allows expressions to be validated at translation time without polluting the cache.
+     *
+     * @param pExpressionString the text to parse
+     * @return the parse result
+     * @throws ELException if there was a problem parsing the expression text
+     */
+    public Object parseExpressionUncached(String pExpressionString) throws ELException {
+        try {
             Reader r = new StringReader(pExpressionString);
             ELParser parser = new ELParser(r);
-            try {
-                ret = parser.ExpressionString();
-                if (!mBypassCache) {
-                    sCachedExpressionStrings.put(pExpressionString, ret);
-                }
-            }
-            catch (ParseException exc) {
-                throw new ELException
-                        (formatParseException(pExpressionString,
-                                exc));
-            }
-            catch (TokenMgrError exc) {
-                // Note - this should never be reached, since the parser is
-                // constructed to tokenize any input (illegal inputs get
-                // parsed to <BADLY_ESCAPED_STRING_LITERAL> or
-                // <ILLEGAL_CHARACTER>
-                throw new ELException(exc.getMessage());
-            }
+            return parser.ExpressionString();
+        } catch (ParseException exc) {
+            throw new ELException(formatParseException(pExpressionString, exc));
+        } catch (TokenMgrError exc) {
+            // Note - this should never be reached, since the parser is
+            // constructed to tokenize any input (illegal inputs get
+            // parsed to <BADLY_ESCAPED_STRING_LITERAL> or
+            // <ILLEGAL_CHARACTER>
+            throw new ELException(exc.getMessage());
         }
-        return ret;
     }
 
     //-------------------------------------
@@ -358,31 +365,35 @@
      * Creates LRU map of expression strings. If context parameter
      * specifying cache size is present use that as the maximum size
      * of the LRU map otherwise use default.
+     *
+     * TODO: Using the context parameter means the cache is sized based on the configuration
+     * of the first web application that calls this. This might be a problem if this jar is
+     * installed in the application server's classpath rather than the application's.
      */
-    private synchronized void createExpressionStringMap() {
-        if (sCachedExpressionStrings != null) {
-            return;
-        }
+    private static synchronized Map getOrCreateExpressionStringMap(PageContext pageContext) {
+        if (sCachedExpressionStrings == null) {
 
-        final int maxSize;
-        if ((pageContext != null) && (pageContext.getServletContext() != null)) {
-            String value = pageContext.getServletContext().getInitParameter(EXPR_CACHE_PARAM);
-            if (value != null) {
-                maxSize = Integer.valueOf(value);
+            final int maxSize;
+            if ((pageContext != null) && (pageContext.getServletContext() != null)) {
+                String value = pageContext.getServletContext().getInitParameter(EXPR_CACHE_PARAM);
+                if (value != null) {
+                    maxSize = Integer.valueOf(value);
+                } else {
+                    maxSize = MAX_SIZE;
+                }
             } else {
                 maxSize = MAX_SIZE;
             }
-        } else {
-            maxSize = MAX_SIZE;
-        }
 
-        // fall through if it couldn't find the parameter
-        sCachedExpressionStrings = Collections.synchronizedMap(new LinkedHashMap() {
-            @Override
-            protected boolean removeEldestEntry(Map.Entry eldest) {
-                return size() > maxSize;
-            }
-        });
+            // fall through if it couldn't find the parameter
+            sCachedExpressionStrings = Collections.synchronizedMap(new LinkedHashMap() {
+                @Override
+                protected boolean removeEldestEntry(Map.Entry eldest) {
+                    return size() > maxSize;
+                }
+            });
+        }
+        return sCachedExpressionStrings;
     }
 
     //-------------------------------------
diff --git a/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/Evaluator.java b/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/Evaluator.java
index 2cb53d0..eadd106 100644
--- a/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/Evaluator.java
+++ b/jstlel/src/main/java/org/apache/taglibs/standard/lang/jstl/Evaluator.java
@@ -67,9 +67,7 @@
     public String validate(String pAttributeName,
                            String pAttributeValue) {
         try {
-            sEvaluator.setBypassCache(true);
-            sEvaluator.parseExpressionString(pAttributeValue);
-            sEvaluator.setBypassCache(false);
+            sEvaluator.parseExpressionUncached(pAttributeValue);
             return null;
         }
         catch (ELException exc) {