/* 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 */ @SuppressWarnings("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 (isSelfKeyword(bean, 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() ; /** * Utility method for checking deprecated use of 'this' in expressions. Checks if * <code>prop</code> == 'this' and logs a Warning message inviting the user to * update to the new keyword. */ static boolean isSelfKeyword(ActionBean bean, Object prop) { boolean isDeprecatedThis = THIS.equals(prop); if (isDeprecatedThis) { // log a message if using the deprecated 'this' keyword. This can happen // if the appserver (e.g. tomcat 7) is configured to skip identifier // check. // this message encourages the user to update EL validation // expressions using the new keyword log.warn("You are using the 'this' keyword in ActionBean class '" + bean.getClass().getName() + "'. It is a reserved keyword. Your application server doesn't seem to complain, but " + "the application could malfunction on other servers. " + "Please use the keyword '" + SELF + "' in replacement of 'this' in your EL expressions."); } return isDeprecatedThis || SELF.equals(prop); } }