| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.struts2.oval.interceptor; |
| |
| import com.opensymphony.xwork2.ActionContext; |
| import com.opensymphony.xwork2.ActionInvocation; |
| import com.opensymphony.xwork2.ActionProxy; |
| import com.opensymphony.xwork2.ModelDriven; |
| import com.opensymphony.xwork2.TextProviderFactory; |
| import com.opensymphony.xwork2.Validateable; |
| import com.opensymphony.xwork2.inject.Inject; |
| import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; |
| import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil; |
| import com.opensymphony.xwork2.util.ValueStack; |
| import net.sf.oval.exception.ExpressionEvaluationException; |
| import net.sf.oval.expression.ExpressionLanguage; |
| import net.sf.oval.expression.ExpressionLanguageOGNLImpl; |
| import ognl.Ognl; |
| import ognl.OgnlException; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| import com.opensymphony.xwork2.validator.DelegatingValidatorContext; |
| import com.opensymphony.xwork2.validator.ValidatorContext; |
| import net.sf.oval.ConstraintViolation; |
| import net.sf.oval.Validator; |
| import net.sf.oval.configuration.Configurer; |
| import net.sf.oval.context.FieldContext; |
| import net.sf.oval.context.MethodReturnValueContext; |
| import net.sf.oval.context.OValContext; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.struts2.oval.annotation.Profiles; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.List; |
| import java.util.Map; |
| |
| /* |
| This interceptor provides validation using the OVal validation framework |
| */ |
| public class OValValidationInterceptor extends MethodFilterInterceptor { |
| |
| public static final String STRUTS_OVAL_VALIDATE_JPAANNOTATIONS = "struts.oval.validateJPAAnnotations"; |
| |
| private static final Logger LOG = LogManager.getLogger(OValValidationInterceptor.class); |
| |
| protected final static String VALIDATE_PREFIX = "validate"; |
| protected final static String ALT_VALIDATE_PREFIX = "validateDo"; |
| |
| protected boolean alwaysInvokeValidate = true; |
| protected boolean programmatic = true; |
| protected OValValidationManager validationManager; |
| protected boolean validateJPAAnnotations; |
| protected TextProviderFactory textProviderFactory; |
| |
| private ExpressionLanguage ognlExpressionLanguage; |
| |
| public OValValidationInterceptor() { |
| ognlExpressionLanguage = new ExpressionLanguageOGNL(); |
| } |
| |
| @Inject |
| public void setValidationManager(OValValidationManager validationManager) { |
| this.validationManager = validationManager; |
| } |
| |
| @Inject |
| public void setTextProviderFactory(TextProviderFactory textProviderFactory) { |
| this.textProviderFactory = textProviderFactory; |
| } |
| |
| /** |
| * Enable OVal support for JPA |
| */ |
| @Inject(value = STRUTS_OVAL_VALIDATE_JPAANNOTATIONS) |
| public void setValidateJPAAnnotations(String validateJPAAnnotations) { |
| this.validateJPAAnnotations = Boolean.parseBoolean(validateJPAAnnotations); |
| } |
| |
| /** |
| * Determines if {@link com.opensymphony.xwork2.Validateable}'s <code>validate()</code> should be called, |
| * as well as methods whose name that start with "validate". Defaults to "true". |
| * |
| * @param programmatic <tt>true</tt> then <code>validate()</code> is invoked. |
| */ |
| public void setProgrammatic(boolean programmatic) { |
| this.programmatic = programmatic; |
| } |
| |
| /** |
| * Determines if {@link com.opensymphony.xwork2.Validateable}'s <code>validate()</code> should always |
| * be invoked. Default to "true". |
| * |
| * @param alwaysInvokeValidate <tt>true</tt> then <code>validate()</code> is always invoked. |
| */ |
| public void setAlwaysInvokeValidate(String alwaysInvokeValidate) { |
| this.alwaysInvokeValidate = Boolean.parseBoolean(alwaysInvokeValidate); |
| } |
| |
| protected String doIntercept(ActionInvocation invocation) throws Exception { |
| Object action = invocation.getAction(); |
| ActionProxy proxy = invocation.getProxy(); |
| ValueStack valueStack = invocation.getStack(); |
| String methodName = proxy.getMethod(); |
| String context = proxy.getConfig().getName(); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Validating [{}/{}] with method [{}]", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName(), methodName); |
| } |
| |
| //OVal vallidatio (no XML yet) |
| performOValValidation(action, valueStack, methodName, context); |
| |
| //Validatable.valiedate() and validateX() |
| performProgrammaticValidation(invocation, action); |
| |
| return invocation.invoke(); |
| } |
| |
| private void performProgrammaticValidation(ActionInvocation invocation, Object action) throws Exception { |
| if (action instanceof Validateable && programmatic) { |
| // keep exception that might occured in validateXXX or validateDoXXX |
| Exception exception = null; |
| |
| Validateable validateable = (Validateable) action; |
| LOG.debug("Invoking validate() on action [{}]", validateable); |
| |
| try { |
| PrefixMethodInvocationUtil.invokePrefixMethod( |
| invocation, |
| new String[]{VALIDATE_PREFIX, ALT_VALIDATE_PREFIX}); |
| } catch (Exception e) { |
| // If any exception occurred while doing reflection, we want |
| // validate() to be executed |
| LOG.warn("An exception occurred while executing the prefix method", e); |
| exception = e; |
| } |
| |
| if (alwaysInvokeValidate) { |
| validateable.validate(); |
| } |
| |
| if (exception != null) { |
| // rethrow if something is wrong while doing validateXXX / validateDoXXX |
| throw exception; |
| } |
| } |
| } |
| |
| protected void performOValValidation(Object action, ValueStack valueStack, String methodName, String context) throws NoSuchMethodException { |
| Class clazz = action.getClass(); |
| //read validation from xmls |
| List<Configurer> configurers = validationManager.getConfigurers(clazz, context, validateJPAAnnotations); |
| |
| Validator validator = configurers.isEmpty() ? new Validator() : new Validator(configurers); |
| // Note: For Oval <= 1.70, API requires "validator.addExpressionLanguage("ognl", ognlExpressionLanguage)". |
| validator.getExpressionLanguageRegistry().registerExpressionLanguage("ognl", ognlExpressionLanguage); // Usage for Oval >= 1.80 due to API changes |
| //if the method is annotated with a @Profiles annotation, use those profiles |
| Method method = clazz.getMethod(methodName, new Class[0]); |
| if (method != null) { |
| Profiles profiles = method.getAnnotation(Profiles.class); |
| if (profiles != null) { |
| String[] profileNames = profiles.value(); |
| if (profileNames != null && profileNames.length > 0) { |
| validator.disableAllProfiles(); |
| LOG.debug("Enabling profiles [{}]", StringUtils.join(profileNames, ",")); |
| for (String profileName : profileNames) |
| validator.enableProfile(profileName); |
| } |
| } |
| } |
| |
| //perform validation |
| List<ConstraintViolation> violations = validator.validate(action); |
| addValidationErrors(violations.toArray(new ConstraintViolation[violations.size()]), action, valueStack, null); |
| } |
| |
| private void addValidationErrors(ConstraintViolation[] violations, Object action, ValueStack valueStack, String parentFieldname) { |
| if (violations != null) { |
| ValidatorContext validatorContext = new DelegatingValidatorContext(action, textProviderFactory); |
| for (ConstraintViolation violation : violations) { |
| //translate message |
| String key = violation.getMessage(); |
| |
| String message = key; |
| // push context variable into stack, to allow use ${max}, ${min} etc in error messages |
| valueStack.push(violation.getMessageVariables()); |
| //push the validator into the stack |
| valueStack.push(violation.getContext()); |
| try { |
| message = validatorContext.getText(key); |
| } finally { |
| valueStack.pop(); |
| valueStack.pop(); |
| } |
| |
| if (isActionError(violation)) { |
| LOG.debug("Adding action error '{}'", message); |
| validatorContext.addActionError(message); |
| } else { |
| ValidationError validationError = buildValidationError(violation, message); |
| |
| // build field name |
| String fieldName = validationError.getFieldName(); |
| if (parentFieldname != null) { |
| fieldName = parentFieldname + "." + fieldName; |
| } |
| |
| LOG.debug("Adding field error [{}] with message '{}'", fieldName, validationError.getMessage()); |
| validatorContext.addFieldError(fieldName, validationError.getMessage()); |
| |
| // don't add "model." prefix to fields of model in model driven action |
| if ((action instanceof ModelDriven) && "model".equals(fieldName)) { |
| fieldName = null; |
| } |
| |
| // add violations of member object fields |
| addValidationErrors(violation.getCauses(), action, valueStack, fieldName); |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Get field name and message, used to add the validation error to fieldErrors |
| */ |
| protected ValidationError buildValidationError(ConstraintViolation violation, String message) { |
| OValContext context = violation.getContext(); |
| if (context instanceof FieldContext) { |
| Field field = ((FieldContext) context).getField(); |
| String className = field.getDeclaringClass().getName(); |
| |
| //the default OVal message shows the field name as ActionClass.fieldName |
| String finalMessage = StringUtils.removeStart(message, className + "."); |
| |
| return new ValidationError(field.getName(), finalMessage); |
| } else if (context instanceof MethodReturnValueContext) { |
| Method method = ((MethodReturnValueContext) context).getMethod(); |
| String className = method.getDeclaringClass().getName(); |
| String methodName = method.getName(); |
| |
| //the default OVal message shows the field name as ActionClass.fieldName |
| String finalMessage = StringUtils.removeStart(message, className + "."); |
| |
| String fieldName = null; |
| if (methodName.startsWith("get")) { |
| fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "get")); |
| } else if (methodName.startsWith("is")) { |
| fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "is")); |
| } |
| |
| //the result will have the full method name, like "getName()", replace it by "name" (obnly if it is a field) |
| if (fieldName != null) |
| finalMessage = finalMessage.replaceAll(methodName + "\\(.*?\\)", fieldName); |
| |
| return new ValidationError(StringUtils.defaultString(fieldName, methodName), finalMessage); |
| } |
| |
| return new ValidationError(violation.getCheckName(), message); |
| } |
| |
| /** |
| * Decide if a violation should be added to the fieldErrors or actionErrors |
| */ |
| protected boolean isActionError(ConstraintViolation violation) { |
| return false; |
| } |
| |
| class ValidationError { |
| private String fieldName; |
| private String message; |
| |
| ValidationError(String fieldName, String message) { |
| this.fieldName = fieldName; |
| this.message = message; |
| } |
| |
| public String getFieldName() { |
| return fieldName; |
| } |
| |
| public String getMessage() { |
| return message; |
| } |
| } |
| |
| } |
| |
| class ExpressionLanguageOGNL extends ExpressionLanguageOGNLImpl { |
| |
| private static final Logger LOG = LogManager.getLogger(ExpressionLanguageOGNL.class); |
| |
| public Object evaluate(final String expression, final Map<String, ? > values) throws ExpressionEvaluationException { |
| try { |
| LOG.debug("Evaluating OGNL expression: {1}", expression); |
| return Ognl.getValue(expression, ActionContext.getContext().getContextMap(), values); |
| } catch (final OgnlException ex) { |
| throw new ExpressionEvaluationException("Evaluating script with OGNL failed.", ex); |
| } |
| } |
| } |
| |