Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form validation #15

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
*/
package org.stripesframework.jsp.tag;

import java.util.Map;

import org.stripesframework.jsp.exception.StripesJspException;
import org.stripesframework.web.action.ActionBean;
import org.stripesframework.web.controller.ParameterName;
import org.stripesframework.web.controller.StripesConstants;
import org.stripesframework.jsp.exception.StripesJspException;
import org.stripesframework.web.util.Log;
import org.stripesframework.web.util.bean.BeanUtil;
import org.stripesframework.web.util.bean.ExpressionException;
import org.stripesframework.web.validation.ValidationMetadata;


/**
Expand Down Expand Up @@ -61,7 +65,19 @@ public Object getValue( InputTagSupport tag ) throws StripesJspException {
boolean kaboom = false;
if ( bean != null ) {
try {
value = BeanUtil.getPropertyValue(tag.getName(), bean);
String propertyName = tag.getName();

Map<String, ValidationMetadata> validationInfos = getConfiguration().getValidationMetadataProvider().getValidationMetadata(bean.getClass());
ValidationMetadata validationInfo = validationInfos.get(new ParameterName(propertyName).getStrippedName());

String fullPropertyName;
if ( validationInfo != null && validationInfo.rootBinding() ) {
fullPropertyName = validationInfo.bindingPrefix() + propertyName;
} else {
fullPropertyName = propertyName;
}

value = BeanUtil.getPropertyValue(fullPropertyName, bean);
}
catch ( ExpressionException ee ) {
if ( !StripesConstants.SPECIAL_URL_KEYS.contains(tag.getName()) ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.stripesframework.web.action;

public interface SingleBeanForm<T> extends ActionBean {

T getBean();

void setBean( T initial );
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,17 @@ public ValidationErrors bind( ActionBean bean, ActionBeanContext context, boolea

// Determine the target type
ValidationMetadata validationInfo = validationInfos.get(name.getStrippedName());

String fullPropertyName;
if ( validationInfo != null && validationInfo.rootBinding() ) {
fullPropertyName = validationInfo.bindingPrefix() + pname;
} else {
fullPropertyName = pname;
}

PropertyExpressionEvaluation eval;
try {
eval = new PropertyExpressionEvaluation(PropertyExpression.getExpression(pname), bean);
eval = new PropertyExpressionEvaluation(PropertyExpression.getExpression(fullPropertyName), bean);
}
catch ( Exception e ) {
if ( pname.equals(context.getEventName()) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
package org.stripesframework.web.controller;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
Expand All @@ -34,14 +37,19 @@
import org.stripesframework.web.action.DontBind;
import org.stripesframework.web.action.DontValidate;
import org.stripesframework.web.action.Resolution;
import org.stripesframework.web.action.SingleBeanForm;
import org.stripesframework.web.config.Configuration;
import org.stripesframework.web.exception.StripesServletException;
import org.stripesframework.web.util.CollectionUtil;
import org.stripesframework.web.util.HtmlUtil;
import org.stripesframework.web.util.Log;
import org.stripesframework.web.util.bean.BeanUtil;
import org.stripesframework.web.validation.FormValidation;
import org.stripesframework.web.validation.ValidateForm;
import org.stripesframework.web.validation.ValidationError;
import org.stripesframework.web.validation.ValidationErrorHandler;
import org.stripesframework.web.validation.ValidationErrors;
import org.stripesframework.web.validation.ValidationMetadata;
import org.stripesframework.web.validation.ValidationMethod;
import org.stripesframework.web.validation.ValidationState;

Expand Down Expand Up @@ -152,19 +160,51 @@ public static Resolution doCustomValidation( final ExecutionContext ctx, final b
public Resolution intercept( ExecutionContext context ) throws Exception {
// Run any of the annotated validation methods
Method[] validations = findCustomValidationMethods(bean.getClass());
for ( Method validation : validations ) {
ValidationMethod ann = validation.getAnnotation(ValidationMethod.class);

boolean run = (ann.when() == ValidationState.ALWAYS) || (ann.when() == ValidationState.DEFAULT && alwaysInvokeValidate) || errors.isEmpty();

if ( run && applies(ann, ctx.getActionBeanContext().getEventName()) ) {
Class<?>[] args = validation.getParameterTypes();
if ( args.length == 1 && args[0].equals(ValidationErrors.class) ) {
validation.invoke(bean, errors);
} else {
validation.invoke(bean);
}
doCustomValidation(ctx, bean, validations, alwaysInvokeValidate, errors);

Map<String, FormValidation> forms = context.getForms();
for ( Map.Entry<String, FormValidation> formEntry : forms.entrySet() ) {
FormValidation formValidation = formEntry.getValue();
SingleBeanForm<?> form = formValidation.getForm();

Set<String> on = formValidation.getOn();
if ( on != null && !CollectionUtil.applies(on.toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) {
continue;
}

//noinspection unchecked,rawtypes
((SingleBeanForm)form).setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean));

Method[] formValidations = findCustomValidationMethods(form.getClass());
doCustomValidation(ctx, form, formValidations, alwaysInvokeValidate, errors);
}

Map<String, ValidationMetadata> validationMetadata = StripesFilter.getConfiguration()
.getValidationMetadataProvider()
.getValidationMetadata(bean.getClass());

for ( Map.Entry<String, ValidationMetadata> validationMetadataEntry : validationMetadata.entrySet() ) {
ValidationMetadata formValidation = validationMetadataEntry.getValue();
Class<? extends SingleBeanForm<?>> formClass = formValidation.form();
if ( formClass == null ) {
continue;
}
if ( formClass != ValidateForm.AnyForm.class ) {
continue;
}

Object form = BeanUtil.getPropertyValue(validationMetadataEntry.getKey(), bean);
if ( form == null ) {
continue;
}

Set<String> on = formValidation.on();
if ( on != null && !CollectionUtil.applies(on.toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) {
continue;
}

Method[] formValidations = findCustomValidationMethods(form.getClass());
doCustomValidation(ctx, form, formValidations, alwaysInvokeValidate, errors);
}

fillInValidationErrors(ctx);
Expand Down Expand Up @@ -269,7 +309,7 @@ public static void fillInValidationErrors( ExecutionContext ctx ) {
* @return a Method[] containing all methods marked as custom validations. May return
* an empty array, but never null.
*/
public static Method[] findCustomValidationMethods( Class<? extends ActionBean> type ) throws Exception {
public static Method[] findCustomValidationMethods( Class<?> type ) throws Exception {
Method[] validations = null;
WeakReference<Method[]> ref = customValidations.get(type);
if ( ref != null ) {
Expand Down Expand Up @@ -445,8 +485,11 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception {
// Look up the ActionBean and set it on the context
ActionBeanContext context = ctx.getActionBeanContext();
ActionBean bean = StripesFilter.getConfiguration().getActionResolver().getActionBean(context);

ctx.setActionBean(bean);

Class<? extends ActionBean> beanClass = bean.getClass();

// Prefer the context from the resolved bean if it differs from the ExecutionContext
if ( context != bean.getContext() ) {
ActionBeanContext other = bean.getContext();
Expand All @@ -458,6 +501,31 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception {
ctx.setActionBeanContext(context);
}

Map<String, ValidationMetadata> validationMetadata = StripesFilter.getConfiguration()
.getValidationMetadataProvider()
.getValidationMetadata(beanClass);

Map<String, FormValidation> additionalForms = new HashMap<>();
for ( Map.Entry<String, ValidationMetadata> validationMetadataEntry : validationMetadata.entrySet() ) {
Class<? extends SingleBeanForm<?>> formClass = validationMetadataEntry.getValue().form();
if ( formClass == null ) {
continue;
}
if ( formClass == ValidateForm.AnyForm.class ) {
continue;
}

SingleBeanForm<?> form = StripesFilter.getConfiguration().getObjectFactory().newInstance(formClass);
form.setContext(context);

FormValidation formValidation = new FormValidation();
formValidation.setForm(form);
formValidation.setOn(validationMetadataEntry.getValue().on());

additionalForms.put(validationMetadataEntry.getKey(), formValidation);
}
ctx.setForms(additionalForms);

// Then register it in the Request as THE ActionBean for this request
HttpServletRequest request = context.getRequest();
request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN, bean);
Expand Down Expand Up @@ -516,4 +584,22 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception {
}
});
}

private static void doCustomValidation( ExecutionContext ctx, Object bean, Method[] validations, boolean alwaysInvokeValidate, ValidationErrors errors )
throws IllegalAccessException, InvocationTargetException {
for ( Method validation : validations ) {
ValidationMethod ann = validation.getAnnotation(ValidationMethod.class);

boolean run = (ann.when() == ValidationState.ALWAYS) || (ann.when() == ValidationState.DEFAULT && alwaysInvokeValidate) || errors.isEmpty();

if ( run && applies(ann, ctx.getActionBeanContext().getEventName()) ) {
Class<?>[] args = validation.getParameterTypes();
if ( args.length == 1 && args[0].equals(ValidationErrors.class) ) {
validation.invoke(bean, errors);
} else {
validation.invoke(bean);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.stripesframework.web.action.ActionBean;
import org.stripesframework.web.action.ActionBeanContext;
import org.stripesframework.web.action.Resolution;
import org.stripesframework.web.util.Log;
import org.stripesframework.web.validation.FormValidation;


/**
Expand All @@ -47,15 +49,16 @@ public static ExecutionContext currentContext() {
return currentContext.get();
}

private Collection<Interceptor> _interceptors;
private Iterator<Interceptor> _iterator;
private Interceptor _target;
private ActionBeanContext _actionBeanContext;
private ActionBean _actionBean;
private Method _handler;
private Resolution _resolution;
private LifecycleStage _lifecycleStage;
private boolean _resolutionFromHandler = false;
private Collection<Interceptor> _interceptors;
private Iterator<Interceptor> _iterator;
private Interceptor _target;
private ActionBeanContext _actionBeanContext;
private ActionBean _actionBean;
private Method _handler;
private Resolution _resolution;
private LifecycleStage _lifecycleStage;
private boolean _resolutionFromHandler = false;
private Map<String, FormValidation> _forms;

/**
* Retrieves the ActionBean instance that is associated with the current request. Available
Expand All @@ -75,6 +78,10 @@ public ActionBeanContext getActionBeanContext() {
return _actionBeanContext;
}

public Map<String, FormValidation> getForms() {
return _forms;
}

/**
* Retrieves the handler Method that is targeted by the current request. Available
* to interceptors only after {@link LifecycleStage#HandlerResolution} has occurred.
Expand Down Expand Up @@ -131,7 +138,11 @@ public Resolution proceed() throws Exception {

/** Sets the ActionBeanContext for the current request. */
public void setActionBeanContext( ActionBeanContext actionBeanContext ) {
_actionBeanContext = actionBeanContext;
_actionBeanContext = actionBeanContext;
}

public void setForms( Map<String, FormValidation> forms ) {
_forms = forms;
}

/** Sets the handler method that will be invoked to process the current request. */
Expand All @@ -149,14 +160,14 @@ public void setInterceptors( Collection<Interceptor> stack ) {

/** Sets the current stage in the request processing lifecycle. */
public void setLifecycleStage( LifecycleStage lifecycleStage ) {
_lifecycleStage = lifecycleStage;
_lifecycleStage = lifecycleStage;
}

/** Sets the Resolution that will be executed to terminate this execution. */
public void setResolution( Resolution resolution ) { _resolution = resolution; }

public void setResolutionFromHandler( boolean resolutionFromHandler ) {
_resolutionFromHandler = resolutionFromHandler;
_resolutionFromHandler = resolutionFromHandler;
}

/**
Expand All @@ -170,7 +181,7 @@ public void setResolutionFromHandler( boolean resolutionFromHandler ) {
* @throws Exception if the lifecycle code or an interceptor throws an Exception
*/
public Resolution wrap( Interceptor target ) throws Exception {
_target = target;
_target = target;
_iterator = null;

// Before executing RequestInit, set this as the current execution context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import java.util.List;
import java.util.Map;

import org.stripesframework.web.action.ActionBean;
import org.stripesframework.web.action.ActionBeanContext;
import org.stripesframework.web.controller.ExecutionContext;
import org.stripesframework.web.controller.StripesFilter;
import org.stripesframework.web.util.ReflectUtil;

Expand Down Expand Up @@ -689,6 +692,11 @@ private Object getDefaultValue( NodeEvaluation node ) throws EvaluationException
return Array.newInstance(clazz.getComponentType(), 0);
} else if ( clazz.isEnum() ) {
return clazz.getEnumConstants()[0];
} else if ( ActionBean.class.isAssignableFrom(clazz) ) {
ActionBean form = (ActionBean)StripesFilter.getConfiguration().getObjectFactory().newInstance(clazz);
ActionBeanContext actionBeanContext = ExecutionContext.currentContext().getActionBeanContext();
form.setContext(actionBeanContext);
return form;
} else {
return StripesFilter.getConfiguration().getObjectFactory().newInstance(clazz);
}
Expand Down
Loading