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