Revision: 791
          http://stripes.svn.sourceforge.net/stripes/?rev=791&view=rev
Author:   tfenne
Date:     2008-01-21 14:54:43 -0800 (Mon, 21 Jan 2008)

Log Message:
-----------
First cut at a fix for STS-360.  We now have three implementations of EL 
validation available, and the code tries to pick the best one that will work in 
the container. In many cases this will result in an implementation that no 
longer needs to allocate PageContexts in the dispatcher servlet.

Side effects of this include upgrading our Servlet and JSP API jars and adding 
the new EL API.  We'll have to be careful from now on to not accidentally use 
Servlet 2.3 or JSP 2.1 features in the main code.

I'm pretty sure this will need some more testing on a Servlet 2.3 container 
before we release it!

Modified Paths:
--------------
    trunk/stripes/build.xml
    trunk/stripes/lib/build/jsp-api.jar
    trunk/stripes/lib/build/servlet-api.jar
    
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
    trunk/stripes/src/net/sourceforge/stripes/controller/DispatcherServlet.java
    trunk/stripes/src/net/sourceforge/stripes/mock/MockServletContext.java

Added Paths:
-----------
    trunk/stripes/lib/build/el-api.jar
    trunk/stripes/src/net/sourceforge/stripes/validation/expression/
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/CommonsElExpressionExecutor.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutor.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutorSupport.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionValidator.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp20ExpressionExecutor.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp21ExpressionExecutor.java
    trunk/stripes/src/net/sourceforge/stripes/validation/expression/package.html

Modified: trunk/stripes/build.xml
===================================================================
--- trunk/stripes/build.xml     2008-01-21 18:44:14 UTC (rev 790)
+++ trunk/stripes/build.xml     2008-01-21 22:54:43 UTC (rev 791)
@@ -132,10 +132,10 @@
      version="true">
     <classpath location="${java.home}/../lib/tools.jar"/>
     <link href="http://java.sun.com/j2se/1.5.0/docs/api/"/>
-    <link href="http://java.sun.com/j2ee/1.4/docs/api/"/>
+    <link href="http://java.sun.com/javaee/5/docs/api/"/>
     <link href="http://static.springframework.org/spring/docs/1.2.x/api/"/>
     <link href="http://java.sun.com/j2se/1.5.0/docs/guide/apt/mirror/"/>
-    <link href="http://www.slf4j.org/api/"/>
+    <link 
href="http://commons.apache.org/logging/commons-logging-1.1/apidocs/"/>
       <group>
         <title>Core API</title>
         <package name="net.sourceforge.stripes.action"/>
@@ -159,6 +159,7 @@
         <package name="net.sourceforge.stripes.controller.multipart"/>
         <package name="net.sourceforge.stripes.util"/>
         <package name="net.sourceforge.stripes.util.bean"/>
+        <package name="net.sourceforge.stripes.validation.expression"/>
       </group>
     </javadoc>
   </target>

Added: trunk/stripes/lib/build/el-api.jar
===================================================================
(Binary files differ)


Property changes on: trunk/stripes/lib/build/el-api.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Modified: trunk/stripes/lib/build/jsp-api.jar
===================================================================
(Binary files differ)

Modified: trunk/stripes/lib/build/servlet-api.jar
===================================================================
(Binary files differ)

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
   2008-01-21 18:44:14 UTC (rev 790)
+++ 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
   2008-01-21 22:54:43 UTC (rev 791)
@@ -23,13 +23,9 @@
 import net.sourceforge.stripes.util.*;
 import net.sourceforge.stripes.util.bean.*;
 import net.sourceforge.stripes.validation.*;
+import net.sourceforge.stripes.validation.expression.ExpressionValidator;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.el.ELException;
-import javax.servlet.jsp.el.Expression;
-import javax.servlet.jsp.el.ExpressionEvaluator;
-import javax.servlet.jsp.el.VariableResolver;
 import java.lang.reflect.*;
 import java.util.*;
 
@@ -663,8 +659,8 @@
     /**
      * Performs validation of attribute values using a JSP EL expression if 
one is defined in the
      * [EMAIL PROTECTED] @}Validate annotation. The expression is evaluated 
once for each value converted.
-     * Makes use of a custom VariableResolver implementation to make 
properties of the ActionBean
-     * available.
+     * See [EMAIL PROTECTED] 
net.sourceforge.stripes.validation.expression.ExpressionValidator} for details
+     * on how this is implemented.
      * 
      * @param bean the ActionBean who's property is being validated
      * @param name the name of the property being validated
@@ -674,59 +670,9 @@
      */
     protected void doExpressionValidation(ActionBean bean, ParameterName name, 
List<Object> values,
             ValidationMetadata validationInfo, ValidationErrors errors) {
-        // If a validation expression was supplied, see if we can process it!
-        Expression expr = null;
-        DelegatingVariableResolver resolver = null;
 
-        if (validationInfo.expression() != null) {
-            final PageContext context = DispatcherHelper.getPageContext();
-
-            if (context == null) {
-                log.error("Could not process expression based validation. It 
would seem that ",
-                        "your servlet container is being mean and will not let 
the dispatcher ",
-                        "servlet manufacture a PageContext object through the 
JSPFactory. The ",
-                        "result of this is that expression validation will be 
disabled. Sorry.");
-            }
-            else {
-                try {
-                    // If this turns out to be slow we could probably cache 
the parsed expression
-                    String expression = validationInfo.expression();
-                    ExpressionEvaluator evaluator = 
context.getExpressionEvaluator();
-                    expr = evaluator.parseExpression(expression, 
Boolean.class, null);
-                    resolver = new DelegatingVariableResolver(bean, 
context.getVariableResolver());
-                }
-                catch (ELException ele) {
-                    throw new StripesRuntimeException(
-                            "Could not parse the EL expression being "
-                                    + "used to validate field "
-                                    + name.getName()
-                                    + ". This is "
-                                    + "not a transient error. Please double 
check the following expression "
-                                    + "for errors: " + 
validationInfo.expression(), ele);
-                }
-            }
-        }
-
-        for (Object value : values) {
-            // And then if we have an expression to use
-            if (expr != null) {
-                try {
-                    resolver.setCurrentValue(value);
-                    Boolean result = (Boolean) expr.evaluate(resolver);
-                    if (!Boolean.TRUE.equals(result)) {
-                        ValidationError error = new 
ScopedLocalizableError("validation.expression",
-                                "valueFailedExpression");
-                        error.setFieldValue(String.valueOf(value));
-                        errors.add(name.getName(), error);
-                    }
-                }
-                catch (ELException ele) {
-                    log.error("Error evaluating expression for property ", 
name.getName(),
-                            " of class ", bean.getClass().getSimpleName(), ". 
Expression: ",
-                            validationInfo.expression());
-                }
-            }
-        }
+        if (validationInfo.expression() != null)
+            ExpressionValidator.evaluate(bean, name, values, validationInfo, 
errors);
     }
 
     /**
@@ -851,56 +797,3 @@
         }
     }
 }
-
-/**
- * A JSP EL VariableResolver that first attempts to look up the value of the 
variable as a first
- * level property on the ActionBean, and if does not exist then delegates to 
the built in resolver.
- * 
- * @author Tim Fennell
- * @since Stripes 1.3
- */
-class DelegatingVariableResolver implements VariableResolver {
-    private ActionBean bean;
-    private VariableResolver delegate;
-    private Object currentValue;
-
-    /** Constructs a resolver based on the action bean and resolver supplied. 
*/
-    DelegatingVariableResolver(ActionBean bean, VariableResolver resolver) {
-        this.bean = bean;
-        this.delegate = resolver;
-    }
-
-    /** Sets the value that the 'this' variable will point at. */
-    void setCurrentValue(Object value) {
-        this.currentValue = value;
-    }
-
-    /**
-     * First tries to fish the property off the ActionBean and if that fails 
delegates to the
-     * contained variable resolver.
-     * 
-     * @param property the name of the variable/property being looked for
-     * @return
-     * @throws ELException
-     */
-    public Object resolveVariable(String property) throws ELException {
-        if ("this".equals(property)) {
-            return this.currentValue;
-        }
-        else if ("actionBean".equals(property)) {
-            return this.bean;
-        }
-        else {
-            Object result = null;
-            try {
-                result = BeanUtil.getPropertyValue(property, bean);
-            }
-            catch (Exception e) { /* Do nothing, this isn't unexpected. */ }
-
-            if (result == null) {
-                result = delegate.resolveVariable(property);
-            }
-            return result;
-        }
-    }
-}

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/DispatcherServlet.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/controller/DispatcherServlet.java 
2008-01-21 18:44:14 UTC (rev 790)
+++ trunk/stripes/src/net/sourceforge/stripes/controller/DispatcherServlet.java 
2008-01-21 22:54:43 UTC (rev 791)
@@ -21,6 +21,8 @@
 import net.sourceforge.stripes.exception.StripesServletException;
 import net.sourceforge.stripes.util.Log;
 import net.sourceforge.stripes.validation.BooleanTypeConverter;
+import net.sourceforge.stripes.validation.expression.ExpressionValidator;
+import net.sourceforge.stripes.validation.expression.Jsp20ExpressionExecutor;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -112,20 +114,25 @@
             // Then setup the ExecutionContext that we'll use to process this 
request
             ctx.setActionBeanContext(context);
 
-            // It's unclear whether this usage of the JspFactory will work in 
all containers. It looks
-            // like it should, but still, we should be careful not to screw up 
regular request
-            // processing if it should fail. Why do we do this?  So we can 
have a container-agnostic
-            // way of getting an ExpressionEvaluator to do expression based 
validation
             try {
                 ActionBeanContext abc = ctx.getActionBeanContext();
-                pageContext = 
JspFactory.getDefaultFactory().getPageContext(this, // the servlet inst
-                                                                            
abc.getRequest(), // req
-                                                                            
abc.getResponse(), // res
-                                                                            
null,   // error page url
-                                                                            
(request.getSession(false) != null), // needsSession - don't force a session 
creation if one doesn't already exist
-                                                                            
abc.getResponse().getBufferSize(),
-                                                                            
true); // autoflush
-                DispatcherHelper.setPageContext(pageContext);
+
+                // It's unclear whether this usage of the JspFactory will work 
in all containers. It looks
+                // like it should, but still, we should be careful not to 
screw up regular request
+                // processing if it should fail. Why do we do this?  So we can 
have a container-agnostic
+                // way of getting an ExpressionEvaluator to do expression 
based validation. And we only
+                // need it if the Jsp20 executor is used, so maybe soon we can 
kill it?
+                if (ExpressionValidator.getExecutor() instanceof 
Jsp20ExpressionExecutor) {
+                    pageContext = 
JspFactory.getDefaultFactory().getPageContext(this, // the servlet inst
+                                                                               
 abc.getRequest(), // req
+                                                                               
 abc.getResponse(), // res
+                                                                               
 null,   // error page url
+                                                                               
 (request.getSession(false) != null), // needsSession - don't force a session 
creation if one doesn't already exist
+                                                                               
 abc.getResponse().getBufferSize(),
+                                                                               
 true); // autoflush
+                    DispatcherHelper.setPageContext(pageContext);
+                }
+
             }
             catch (Exception e) {
                 // Don't even log this, this failure gets reported if action 
beans actually

Modified: trunk/stripes/src/net/sourceforge/stripes/mock/MockServletContext.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/mock/MockServletContext.java      
2008-01-21 18:44:14 UTC (rev 790)
+++ trunk/stripes/src/net/sourceforge/stripes/mock/MockServletContext.java      
2008-01-21 22:54:43 UTC (rev 791)
@@ -73,6 +73,11 @@
         }
     }
 
+    /** Servlet 2.3 method. Returns the context name with a leading slash. */
+    public String getContextPath() {
+        return "/" + this.contextName;
+    }
+
     /** Always returns 2. */
     public int getMajorVersion() { return 2; }
 

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/CommonsElExpressionExecutor.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/CommonsElExpressionExecutor.java
                            (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/CommonsElExpressionExecutor.java
    2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,61 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.util.ReflectUtil;
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+
+import javax.servlet.jsp.el.ExpressionEvaluator;
+
+/**
+ * An implementation of [EMAIL PROTECTED] ExpressionExecutor} that relies on 
the Apache Commons
+ * EL implementation being available in the classpath. This is the case with 
Tomcat
+ * 5.5 and can be made so with other containers by including commons-el.jar in 
the web
+ * application's classpath.
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
[EMAIL PROTECTED]("deprecation")
+public class CommonsElExpressionExecutor extends ExpressionExecutorSupport {
+    /** The FQN of the expression evaluator class in commons-el. */
+    public static final String COMMONS_CLASS = 
"org.apache.commons.el.ExpressionEvaluatorImpl";
+
+    /**
+     * Default constructor that checks to make sure this class can work, and 
if not throws
+     * an exception.
+     */
+    public CommonsElExpressionExecutor() {
+        if (getEvaluator() == null) {
+            throw new StripesRuntimeException("Apache commons EL does not 
appear to be available.");
+        }
+    }
+
+    /**
+     * Attempts to create an expression evaluator by reflecting to find the 
implementation
+     * in the apache commons-el project.
+     *
+     * @return an instance of ExpressionEvaluatorImpl if it can, null otherwise
+     */
+    protected ExpressionEvaluator getEvaluator() {
+        try {
+            return (ExpressionEvaluator)
+                    ReflectUtil.findClass(COMMONS_CLASS).newInstance();
+        }
+        catch (Exception e) {
+            return null;
+        }
+    }
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutor.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutor.java
                             (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutor.java
     2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,59 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.controller.ParameterName;
+import net.sourceforge.stripes.validation.ValidationMetadata;
+import net.sourceforge.stripes.validation.ValidationErrors;
+
+import java.util.List;
+
+/**
+ * <p>Simple interface that specifies how Stripes will invoke expression based 
validation.
+ * Generally used via the ExpressionValidator which will pick an appropriate 
implementation
+ * based on the current environment.</p>
+ *
+ * <p>Implementations should throw an exception from their default constructor 
if they
+ * are unable to operate due to class versioning of availability issues.</p>
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
+public interface ExpressionExecutor {
+    /** The default scope to use when constructing errors. */
+    String ERROR_DEFAULT_SCOPE = "validation.expression";
+
+    /** The error key to use when constructing errors. */
+    String ERROR_KEY = "valueFailedExpression";
+
+    /** The special name given to the field that the expression is annotated 
on. */
+    String THIS = "this";
+
+    /**
+     * Performs validation of an ActionBean property using the expression 
contained
+     * within the validation metadata. If the expression does not evaluate to 
true
+     * then an error will be added to the validation errors. Otherwise there 
are no
+     * side effects.
+     *
+     * @param bean the ActionBean instance owning the field being validated
+     * @param name the name of the field being validated
+     * @param values the List of values (post type conversion), each to be 
validated
+     * @param validationInfo the validation metadata for the field
+     * @param errors the ValidationErrors object into which to place any errors
+     */
+    public void evaluate(ActionBean bean, ParameterName name, List<Object> 
values,
+                         ValidationMetadata validationInfo, ValidationErrors 
errors);
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutorSupport.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutorSupport.java
                              (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionExecutorSupport.java
      2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,145 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.util.bean.BeanUtil;
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.controller.ParameterName;
+import net.sourceforge.stripes.controller.StripesConstants;
+import net.sourceforge.stripes.validation.ValidationMetadata;
+import net.sourceforge.stripes.validation.ValidationErrors;
+import net.sourceforge.stripes.validation.ValidationError;
+import net.sourceforge.stripes.validation.ScopedLocalizableError;
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+
+import javax.servlet.jsp.el.VariableResolver;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import java.util.List;
+
+/**
+ * A base class that provides the general plumbing for running expression 
validation
+ * using the old JSP 2.0 style ExpressionEvaluator. Uses a custom 
VariableResolver
+ * to make fields of the ActionBean available in the expression.
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
[EMAIL PROTECTED]("deprecation")
+public abstract class ExpressionExecutorSupport implements ExpressionExecutor {
+    private static final Log log = 
Log.getInstance(ExpressionExecutorSupport.class);
+
+    /**
+     * A JSP EL VariableResolver that first attempts to look up the value of 
the variable as a first
+     * level property on the ActionBean, and if does not exist then delegates 
to the built in resolver.
+     *
+     * @author Tim Fennell
+     * @since Stripes 1.3
+     */
+    protected static class BeanVariableResolver implements VariableResolver {
+        private ActionBean bean;
+        private Object currentValue;
+
+        /** Constructs a resolver based on the action bean . */
+        BeanVariableResolver(ActionBean bean) {
+            this.bean = bean;
+        }
+
+        /** Sets the value that the 'this' variable will point at. */
+        void setCurrentValue(Object value) {
+            this.currentValue = value;
+        }
+
+        /**
+         * Recognizes a couple of special variables, and if the property 
requested
+         * isn't one of them, just looks up a property on the action bean.
+         *
+         * @param property the name of the variable/property being looked for
+         * @return the property value or null
+         * @throws javax.servlet.jsp.el.ELException
+         */
+        public Object resolveVariable(String property) throws ELException {
+            if (ExpressionExecutor.THIS.equals(property)) {
+                return this.currentValue;
+            }
+            else if (StripesConstants.REQ_ATTR_ACTION_BEAN.equals(property)) {
+                return this.bean;
+            }
+            else {
+                try { return BeanUtil.getPropertyValue(property, bean); }
+                catch (Exception e) { return null; }
+            }
+        }
+    }
+
+    // See interface for javadoc
+    public void evaluate(final ActionBean bean, final ParameterName name, 
final List<Object> values,
+                         final ValidationMetadata validationInfo, final 
ValidationErrors errors) {
+        Expression expr = null;
+        BeanVariableResolver resolver = null;
+
+        if (validationInfo.expression() != null) {
+            try {
+                // Make sure we can get an evaluator
+                ExpressionEvaluator evaluator = getEvaluator();
+                if (evaluator == null) return;
+
+                // If this turns out to be slow we could probably cache the 
parsed expression
+                String expression = validationInfo.expression();
+                expr = evaluator.parseExpression(expression, Boolean.class, 
null);
+                resolver = new BeanVariableResolver(bean);
+            }
+            catch (ELException ele) {
+                throw new StripesRuntimeException(
+                        "Could not parse the EL expression being used to 
validate field " +
+                        name.getName() + ". This is not a transient error. 
Please double " +
+                        "check the following expression for errors: " +
+                        validationInfo.expression(), ele);
+            }
+        }
+
+        // Then validate each value we have and add error messages
+        for (Object value : values) {
+            // And then if we have an expression to use
+            if (expr != null) {
+                try {
+                    resolver.setCurrentValue(value);
+                    Boolean result = (Boolean) expr.evaluate(resolver);
+                    if (!Boolean.TRUE.equals(result)) {
+                        ValidationError error = new 
ScopedLocalizableError(ERROR_DEFAULT_SCOPE,
+                                                                           
ERROR_KEY);
+                        error.setFieldValue(String.valueOf(value));
+                        errors.add(name.getName(), error);
+                    }
+                }
+                catch (ELException ele) {
+                    log.error("Error evaluating expression for property ", 
name.getName(),
+                              " of class ", bean.getClass().getSimpleName(), 
". Expression: ",
+                              validationInfo.expression());
+                }
+            }
+        }
+    }
+
+    /**
+     * Must be implemented by subclasses to return an instance of 
ExpressionEvaluator
+     * that can be used to execute expressions.
+     *
+     * @return a working ExpressionEvaluator implementation.
+     */
+    protected abstract ExpressionEvaluator getEvaluator() ;
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionValidator.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionValidator.java
                            (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/ExpressionValidator.java
    2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,98 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.controller.ParameterName;
+import net.sourceforge.stripes.validation.ValidationMetadata;
+import net.sourceforge.stripes.validation.ValidationErrors;
+import net.sourceforge.stripes.util.Log;
+
+import java.util.List;
+
+/**
+ * <p>A Facade to the classes that perform expression based validation. Hides 
the fact that we
+ * might be using one of many implementations to actually run expression 
validation. When the
+ * [EMAIL PROTECTED] 
net.sourceforge.stripes.validation.expression.ExpressionExecutor} is first 
requested
+ * an attempt is made to find the best working executor available.  The 
following classes will
+ * be tried in turn until a working instance is found:</p>
+ *
+ * <ul>
+ *   <li>[EMAIL PROTECTED] Jsp21ExpressionExecutor}</li>
+ *   <li>[EMAIL PROTECTED] CommonsElExpressionExecutor}</li>
+ *   <li>[EMAIL PROTECTED] Jsp20ExpressionExecutor</li>
+ * </ul>
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
+public class ExpressionValidator {
+    private static final Log log = Log.getInstance(ExpressionValidator.class);
+    private static ExpressionExecutor executor;
+
+    /**
+     * Attempts to instantiate executor classes (as described in the class 
level javadoc)
+     * until a working one is found.
+     */
+    public static void initialize() {
+        try {
+            executor = new Jsp21ExpressionExecutor();
+        }
+        catch (Exception e) {
+            try {
+                executor = new CommonsElExpressionExecutor();
+            }
+            catch (Exception e2) {
+                executor = new Jsp20ExpressionExecutor();
+            }
+        }
+
+        log.info("Expression validation will be performed using: " + 
executor.getClass().getName());
+    }
+
+    /**
+     * Run expression validation on the bean property provided with the values 
provided.
+     *
+     * @param bean the ActionBean being validated
+     * @param name the ParameterName object representing the parameter being 
validated
+     * @param values the values to be validated (zero or more)
+     * @param validationInfo the validation metadata for the named property
+     * @param errors the ValidationErrors for the property, to be added to
+     */
+    public static void evaluate(final ActionBean bean, final ParameterName 
name, final List<Object> values,
+                                final ValidationMetadata validationInfo, final 
ValidationErrors errors) {
+        executor.evaluate(bean, name, values, validationInfo, errors);
+    }
+
+    /**
+     * Gets the executor that will be used to run expression evaluation. If 
none is yet set
+     * the [EMAIL PROTECTED] #initialize()} method will be run to set one up.
+     *
+     * @return an instance of ExpressionExecutor that can be used to execut 
validation expressions
+     */
+    public static ExpressionExecutor getExecutor() {
+        if (executor == null) initialize();
+        return executor;
+    }
+
+    /**
+     * Allows anyone who is interested to substitute a different 
ExpressionExecutor instance
+     * instead of the one picked by the ExpressionValidator.
+     * @param executor the executor to use from now on
+     */
+    public static void setExecutor(final ExpressionExecutor executor) {
+        ExpressionValidator.executor = executor;
+    }
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp20ExpressionExecutor.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp20ExpressionExecutor.java
                                (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp20ExpressionExecutor.java
        2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,56 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.controller.DispatcherHelper;
+
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * An implementation of [EMAIL PROTECTED] ExpressionExecutor} that uses the 
container's built in
+ * JSP2.0 EL implementation. This requires that the DispatcherServlet 
allocates a
+ * [EMAIL PROTECTED] javax.servlet.jsp.PageContext} object earlier in the 
request cycle in order
+ * to gain access to the ExpressionEvaluator.  This can cause problems in some 
containers.
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
[EMAIL PROTECTED]("deprecation")
+public class Jsp20ExpressionExecutor extends ExpressionExecutorSupport {
+    private static final Log log = 
Log.getInstance(Jsp20ExpressionExecutor.class);
+
+    /**
+     * Attempts to get the PageContext object stashed away in the 
DispatcherHelper
+     *  and use it to generate an ExpressionEvaluator.
+     *
+     * @return an ExpressionEvaluator if possible, or null otherwise
+     */
+    protected ExpressionEvaluator getEvaluator() {
+        final PageContext context = DispatcherHelper.getPageContext();
+
+        if (context == null) {
+            log.error("Could not process expression based validation. It would 
seem that ",
+                      "your servlet container is being mean and will not let 
the dispatcher ",
+                      "servlet manufacture a PageContext object through the 
JSPFactory. The ",
+                      "result of this is that expression validation will be 
disabled. Sorry.");
+            return null;
+        }
+        else {
+            return context.getExpressionEvaluator();
+        }
+    }
+}
\ No newline at end of file

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp21ExpressionExecutor.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp21ExpressionExecutor.java
                                (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/Jsp21ExpressionExecutor.java
        2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,239 @@
+/* Copyright 2008 Tim Fennell
+ *
+ * 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 net.sourceforge.stripes.validation.expression;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.controller.ParameterName;
+import net.sourceforge.stripes.controller.StripesFilter;
+import net.sourceforge.stripes.controller.StripesConstants;
+import net.sourceforge.stripes.validation.ValidationMetadata;
+import net.sourceforge.stripes.validation.ValidationErrors;
+import net.sourceforge.stripes.validation.ValidationError;
+import net.sourceforge.stripes.validation.ScopedLocalizableError;
+import net.sourceforge.stripes.util.Log;
+import net.sourceforge.stripes.util.bean.BeanUtil;
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.ServletContext;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+import javax.el.ELException;
+import java.util.List;
+import java.util.Iterator;
+import java.beans.FeatureDescriptor;
+import java.lang.reflect.Method;
+
+/**
+ * An implementation of [EMAIL PROTECTED] ExpressionExecutor} that uses the 
new EL API available in Java
+ * EE 5 in the [EMAIL PROTECTED] javax.el} package. While more complicated 
that the JSP 2.0 API it has
+ * one advantage which is that it can be used without the need to allocate a 
PageContext
+ * object and without any other libraries being available.
+ *
+ * @author tfenne
+ * @since Stripes 1.5
+ */
+public class Jsp21ExpressionExecutor implements ExpressionExecutor {
+    private static final Log log = 
Log.getInstance(Jsp21ExpressionExecutor.class);
+
+    /**
+     * Implementation of the EL interface to resolve variables. Resolves 
variables by
+     * checking two special names ("this" and "actionBean") and then falling 
back to
+     * retrieving property values from the ActionBean passed in to the 
constructor.
+     *
+     * @author Tim Fennell
+     * @since Stripes 1.5
+     */
+    protected static class StripesELResolver extends ELResolver {
+        private ActionBean bean;
+        private Object currentValue;
+
+        /** Constructs a resolver based on the action bean . */
+        StripesELResolver(ActionBean bean) {
+            this.bean = bean;
+        }
+
+        /** Sets the value that the 'this' variable will point at. */
+        void setCurrentValue(Object value) {
+            this.currentValue = value;
+        }
+
+        /**
+         * Attempts to resolve the value as described in the class level 
javadoc.
+         * @param ctx the ELContext for the expression
+         * @param base the object on which the property resides (null == root 
property)
+         * @param prop the name of the property being looked for
+         * @return the value of the property or null if one can't be found
+         */
+        public Object getValue(ELContext ctx, Object base, Object prop) throws 
{
+            if ("this".equals(prop)) {
+                ctx.setPropertyResolved(true);
+                return this.currentValue;
+            }
+            else if (StripesConstants.REQ_ATTR_ACTION_BEAN.equals(prop)) {
+                ctx.setPropertyResolved(true);
+                return this.bean;
+            }
+            else {
+                try {
+                    base = base == null ? this.bean : base;
+                    Object retval = 
BeanUtil.getPropertyValue(String.valueOf(prop), base);
+                    ctx.setPropertyResolved(true);
+                    return retval;
+                }
+                catch (Exception e) { return null; }
+            }
+        }
+
+        /** Does nothing. Always returns Object.class. */
+        public Class<?> getType(final ELContext ctx, final Object base, final 
Object prop) {
+            ctx.setPropertyResolved(true);
+            return Object.class;
+        }
+
+        /** Does nothing. Always throws PropertyNotWritableException. */
+        public void setValue(ELContext elContext, Object o, Object o1, Object 
o2) throws PropertyNotWritableException {
+            throw new PropertyNotWritableException("Unsupported Op");
+        }
+
+        /** Always returns true. */
+        public boolean isReadOnly(final ELContext elContext, final Object o, 
final Object o1) { return true; }
+
+        /** Always returns null. */
+        public Iterator<FeatureDescriptor> getFeatureDescriptors(final 
ELContext elContext, final Object o) { return null; }
+
+        /** Always returns Object.class. */
+        public Class<?> getCommonPropertyType(final ELContext elContext, final 
Object o) { return Object.class; }
+    }
+
+    /**
+     * Implementation of the EL interface for managing expression context. 
Resolves variables
+     * using the StripesELResolver above.  Both the FunctionMapper and 
VariableResolver are
+     * essentially no-op implementations.
+     *
+     * @author Tim Fennell
+     * @since Stripes 1.5
+     */
+    protected static class StripesELContext extends ELContext {
+        private ActionBean bean;
+        private StripesELResolver resolver;
+        private VariableMapper vmapper;
+        private static final FunctionMapper fmapper = new FunctionMapper() {
+            public Method resolveFunction(final String s, final String s1) { 
return null; }
+        };
+
+        /**
+         * Constructs a new instance using the ActionBean provided as the 
source for most
+         * property resolutions.
+         *
+         * @param bean the ActionBean to resolve properties against
+         */
+        public StripesELContext(ActionBean bean) {
+            this.bean = bean;
+            this.resolver = new StripesELResolver(bean);
+
+            this.vmapper = new VariableMapper() {
+                public ValueExpression resolveVariable(final String s) {
+                    return null;
+                }
+
+                public ValueExpression setVariable(final String s, final 
ValueExpression valueExpression) {
+                    return null;
+                }
+            };
+        }
+
+        /** Sets the current value of the 'this' special variable. */
+        public void setCurrentValue(final Object value) 
{resolver.setCurrentValue(value);}
+
+        /** Returns the StripesELResovler. */
+        public StripesELResolver getELResolver() { return this.resolver; }
+
+        /** Returns a no-op implementation of FunctionMapper. */
+        public FunctionMapper getFunctionMapper() { return fmapper; }
+
+        /** Returns a no-op implementation of VariableMapper. */
+        public VariableMapper getVariableMapper() { return vmapper; }
+    }
+
+    /** Default constructor that throws an exception if the JSP2.1 APIs are 
not available. */
+    public Jsp21ExpressionExecutor() {
+        if (getExpressionFactory() == null) {
+            throw new StripesRuntimeException("Could not create a JSP2.1 
ExpressionFactory.");
+        }
+    }
+
+    // See interface for javadoc.  
+    public void evaluate(final ActionBean bean, final ParameterName name, 
final List<Object> values,
+                         final ValidationMetadata validationInfo, final 
ValidationErrors errors) {
+
+        StripesELContext ctx = null;
+        String expressionString = validationInfo.expression();
+        ValueExpression expression = null;
+
+        try {
+            if (expressionString != null) {
+                // Make sure we can get an factory
+                ExpressionFactory factory = getExpressionFactory();
+                if (factory == null) return;
+
+                ctx = new StripesELContext(bean);
+
+                // If this turns out to be slow we could probably cache the 
parsed expression
+                expression = factory.createValueExpression(ctx, 
expressionString, Boolean.class);
+            }
+        }
+        catch (ELException ele) {
+            throw new StripesRuntimeException(
+                    "Could not parse the EL expression being used to validate 
field " +
+                    name.getName() + ". This is not a transient error. Please 
double " +
+                    "check the following expression for errors: " +
+                    validationInfo.expression(), ele);
+        }
+
+        for (Object value : values) {
+            // And then if we have an expression to use
+            if (expression != null) {
+                try {
+                    ctx.setCurrentValue(value);
+                    Boolean result = (Boolean) expression.getValue(ctx);
+                    if (!Boolean.TRUE.equals(result)) {
+                        ValidationError error = new 
ScopedLocalizableError(ERROR_DEFAULT_SCOPE, ERROR_KEY);
+                        error.setFieldValue(String.valueOf(value));
+                        errors.add(name.getName(), error);
+                    }
+                }
+                catch (ELException ele) {
+                    log.error("Error evaluating expression for property ", 
name.getName(),
+                              " of class ", bean.getClass().getSimpleName(), 
". Expression: ",
+                              validationInfo.expression());
+                }
+            }
+        }
+    }
+
+    /** Creates an ExpressionFactory using the JspApplicationContext. */
+    protected ExpressionFactory getExpressionFactory() {
+        ServletContext ctx = 
StripesFilter.getConfiguration().getServletContext();
+        JspApplicationContext jspCtx = 
JspFactory.getDefaultFactory().getJspApplicationContext(ctx);
+        return jspCtx.getExpressionFactory();
+    }
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/package.html
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/package.html    
                            (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/expression/package.html    
    2008-01-21 22:54:43 UTC (rev 791)
@@ -0,0 +1,4 @@
+<body>
+    <p>This package provides several classes that give Stripes the ability to 
execute
+       EL expressions for validation using various implementations.</p>
+</body>
\ No newline at end of file


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to