From 08418b4d536bdf3f18581d1e0086ae3085bde6bb Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Tue, 24 Nov 2020 12:26:05 +0100 Subject: [PATCH 1/9] added form validation feature --- .../net/sourceforge/stripes/action/Form.java | 9 +++ .../stripes/controller/DispatcherHelper.java | 60 +++++++++++++++ .../stripes/controller/ExecutionContext.java | 29 ++++--- .../DefaultValidationMetadataProvider.java | 76 ++++++++++++++----- .../stripes/validation/FormValidation.java | 32 ++++++++ .../stripes/validation/ValidateForm.java | 42 ++++++++++ .../validation/ValidationMetadata.java | 20 ++++- 7 files changed, 237 insertions(+), 31 deletions(-) create mode 100644 stripes/src/main/java/net/sourceforge/stripes/action/Form.java create mode 100644 stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java create mode 100644 stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Form.java b/stripes/src/main/java/net/sourceforge/stripes/action/Form.java new file mode 100644 index 000000000..eed9af632 --- /dev/null +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Form.java @@ -0,0 +1,9 @@ +package net.sourceforge.stripes.action; + +public interface Form extends ActionBean { + + T getBean(); + + void setBean( T initial ); + +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java index e0267bc65..b39775b5e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java @@ -19,6 +19,7 @@ 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; @@ -34,15 +35,19 @@ import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.DontBind; import net.sourceforge.stripes.action.DontValidate; +import net.sourceforge.stripes.action.Form; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.exception.StripesServletException; import net.sourceforge.stripes.util.CollectionUtil; import net.sourceforge.stripes.util.HtmlUtil; import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.bean.BeanUtil; +import net.sourceforge.stripes.validation.FormValidation; import net.sourceforge.stripes.validation.ValidationError; import net.sourceforge.stripes.validation.ValidationErrorHandler; import net.sourceforge.stripes.validation.ValidationErrors; +import net.sourceforge.stripes.validation.ValidationMetadata; import net.sourceforge.stripes.validation.ValidationMethod; import net.sourceforge.stripes.validation.ValidationState; @@ -171,6 +176,38 @@ public Resolution intercept( ExecutionContext context ) throws Exception { } } + Map forms = context.getForms(); + for ( Map.Entry formEntry : forms.entrySet() ) { + FormValidation formValidation = formEntry.getValue(); + //noinspection rawtypes + Form form = formValidation.getForm(); + + if ( !CollectionUtil.applies(formValidation.getOn().toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) { + continue; + } + + //noinspection unchecked + form.setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean)); + + Method[] formValidations = findCustomValidationMethods(form.getClass()); + for ( Method validation : formValidations ) { + 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(form, errors); + } else { + validation.invoke(form); + } + } + } + + } + fillInValidationErrors(ctx); return null; } @@ -459,8 +496,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 beanClass = bean.getClass(); + // Prefer the context from the resolved bean if it differs from the ExecutionContext if ( context != bean.getContext() ) { ActionBeanContext other = bean.getContext(); @@ -472,6 +512,26 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception { ctx.setActionBeanContext(context); } + Map validationMetadata = StripesFilter.getConfiguration() + .getValidationMetadataProvider() + .getValidationMetadata(beanClass); + Map additionalForms = new HashMap<>(); + for ( Map.Entry validationMetadataEntry : validationMetadata.entrySet() ) { + if ( validationMetadataEntry.getValue().getForm() == null ) { + continue; + } + + Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().getForm()); + 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); diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/ExecutionContext.java b/stripes/src/main/java/net/sourceforge/stripes/controller/ExecutionContext.java index af9c40a5d..321d3bb57 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/ExecutionContext.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/ExecutionContext.java @@ -17,11 +17,13 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Iterator; +import java.util.Map; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.validation.FormValidation; /** @@ -47,15 +49,16 @@ public static ExecutionContext currentContext() { return currentContext.get(); } - private Collection _interceptors; - private Iterator _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 _interceptors; + private Iterator _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 forms; /** * Retrieves the ActionBean instance that is associated with the current request. Available @@ -75,6 +78,10 @@ public ActionBeanContext getActionBeanContext() { return _actionBeanContext; } + public Map getForms() { + return forms; + } + /** * Retrieves the handler Method that is targeted by the current request. Available * to interceptors only after {@link LifecycleStage#HandlerResolution} has occurred. @@ -134,6 +141,10 @@ public void setActionBeanContext( ActionBeanContext actionBeanContext ) { _actionBeanContext = actionBeanContext; } + public void setForms( Map forms ) { + this.forms = forms; + } + /** Sets the handler method that will be invoked to process the current request. */ public void setHandler( Method handler ) { _handler = handler; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java index 53dca08d8..e2af51534 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java @@ -30,6 +30,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import net.sourceforge.stripes.action.Form; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.controller.ParameterName; import net.sourceforge.stripes.exception.StripesRuntimeException; @@ -243,39 +244,78 @@ protected Map loadForClass( Class beanType ) { Map meta = new HashMap<>(); @SuppressWarnings("unchecked") Map annotationInfoMap = getAnnotationInfoMap(beanType, Validate.class, - ValidateNestedProperties.class); + ValidateNestedProperties.class, ValidateForm.class); for ( String propertyName : annotationInfoMap.keySet() ) { AnnotationInfo annotationInfo = annotationInfoMap.get(propertyName); // get the @Validate and/or @ValidateNestedProperties - Validate simple = annotationInfo.getAnnotation(Validate.class); - ValidateNestedProperties nested = annotationInfo.getAnnotation(ValidateNestedProperties.class); + Validate simpleAnnotation = annotationInfo.getAnnotation(Validate.class); + ValidateNestedProperties nestedAnnotation = annotationInfo.getAnnotation(ValidateNestedProperties.class); + ValidateForm formAnnotation = annotationInfo.getAnnotation(ValidateForm.class); Class clazz = annotationInfo.getTargetClass(); // add to allow list if @Validate present - if ( simple != null ) { - if ( simple.field() == null || "".equals(simple.field()) ) { - meta.put(propertyName, new ValidationMetadata(propertyName, simple)); + if ( simpleAnnotation != null ) { + if ( "".equals(simpleAnnotation.field()) ) { + meta.put(propertyName, new ValidationMetadata(propertyName, simpleAnnotation)); } else { - log.warn("Field name present in @Validate but should be omitted: ", clazz, ", property ", propertyName, ", given field name ", simple.field()); + log.warn("Field name present in @Validate but should be omitted: ", clazz, ", property ", propertyName, ", given field name ", + simpleAnnotation.field()); } } // add all sub-properties referenced in @ValidateNestedProperties - if ( nested != null ) { - Validate[] validates = nested.value(); - if ( validates != null ) { - for ( Validate validate : validates ) { - if ( validate.field() != null && !"".equals(validate.field()) ) { - String fullName = propertyName + '.' + validate.field(); - if ( meta.containsKey(fullName) ) { - log.warn("More than one nested @Validate with same field name: " + validate.field() + " on property " + propertyName); - } - meta.put(fullName, new ValidationMetadata(fullName, validate)); + if ( nestedAnnotation != null || formAnnotation != null ) { + Validate[] validates; + String[] additionalOns; + + if ( formAnnotation != null && formAnnotation.form() != Form.class ) { + Class form = formAnnotation.form(); + additionalOns = formAnnotation.on(); + //noinspection unchecked + Map formAnnotationInfoMap = getAnnotationInfoMap(form, Validate.class, ValidateNestedProperties.class); + if ( formAnnotationInfoMap.size() != 1 ) { + log.warn("Form used for proerty ", propertyName, " defines ", formAnnotationInfoMap.size(), + " validations. Expecting exactly 1 for property 'bean'."); + continue; + } else { + AnnotationInfo validatedProperty = formAnnotationInfoMap.get("bean"); + if ( validatedProperty == null ) { + log.warn("Form used for proerty ", propertyName, " does not define a validation for the property 'bean'."); + continue; } else { - log.warn("Field name missing from nested @Validate: ", clazz, ", property ", propertyName); + validates = validatedProperty.getAnnotation(ValidateNestedProperties.class).value(); + } + } + //noinspection unchecked + ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class>)form); + for ( String additionalOn : additionalOns ) { + validationMetadata.on(additionalOn); + } + meta.put(propertyName, validationMetadata); + } else { + additionalOns = new String[0]; + if ( nestedAnnotation != null ) { + validates = nestedAnnotation.value(); + } else { + validates = new Validate[0]; + } + } + + for ( Validate validate : validates ) { + if ( !"".equals(validate.field()) ) { + String fullName = propertyName + '.' + validate.field(); + if ( meta.containsKey(fullName) ) { + log.warn("More than one nestedAnnotation @Validate with same field name: " + validate.field() + " on property " + propertyName); + } + ValidationMetadata validationMetadata = new ValidationMetadata(fullName, validate); + for ( String additionalOn : additionalOns ) { + validationMetadata.on(additionalOn); } + meta.put(fullName, validationMetadata); + } else { + log.warn("Field name missing from nestedAnnotation @Validate: ", clazz, ", property ", propertyName); } } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java new file mode 100644 index 000000000..9d3ba3c57 --- /dev/null +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java @@ -0,0 +1,32 @@ +package net.sourceforge.stripes.validation; + +import java.util.Collections; +import java.util.Set; + +import net.sourceforge.stripes.action.Form; + + +public class FormValidation { + + private Form form; + private Set on; + + public Form getForm() { + return form; + } + + public Set getOn() { + if ( on == null ) { + return Collections.emptySet(); + } + return on; + } + + public void setForm( Form form ) { + this.form = form; + } + + public void setOn( Set on ) { + this.on = on; + } +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java new file mode 100644 index 000000000..14afd5a19 --- /dev/null +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java @@ -0,0 +1,42 @@ +/* Copyright 2005-2006 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.sourceforge.stripes.action.Form; + + +/** + * Annotation used to capture the validation needs of nested properties within an ActionBean. It + * contains a simple array of the Validate annotations. Each Validate annotation must have its + * field property set to the name of the field within the annotated property that is to be validated. + * + * @author Tim Fennell + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Documented +public @interface ValidateForm { + + Class form() default Form.class; + + String[] on() default {}; + +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java index 1d2a58480..5adf3a4c5 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java @@ -18,6 +18,8 @@ import java.util.Set; import java.util.regex.Pattern; +import net.sourceforge.stripes.action.Form; + /** *

Encapsulates the validation metadata for a single property of a single class. Structure @@ -35,6 +37,7 @@ public class ValidationMetadata { private final String _property; + private Class> _form; private boolean _encrypted; private boolean _required; private boolean _trim; @@ -105,10 +108,15 @@ public ValidationMetadata( String property, Validate validate ) { } } + public ValidationMetadata( String property, Class> form ) { + _property = property; + _form = form; + } + /** Sets the overridden TypeConveter to use to convert values. */ @SuppressWarnings("rawtypes") public ValidationMetadata converter( Class converter ) { - _converter = converter; + _converter = converter; return this; } @@ -127,9 +135,9 @@ public ValidationMetadata encrypted( boolean encrypted ) { /** Sets the expression that should be used to validate values. */ public ValidationMetadata expression( String expression ) { - _expression = expression; + _expression = expression; if ( !_expression.startsWith("${") ) { - _expression = "${" + _expression + "}"; + _expression = "${" + _expression + "}"; } return this; } @@ -137,6 +145,10 @@ public ValidationMetadata expression( String expression ) { /** Returns the overridden TypeConverter if there is one, or null. */ public String expression() { return _expression; } + public Class> getForm() { + return _form; + } + /** Returns the name of the property this validation metadata represents. */ public String getProperty() { return _property; @@ -159,7 +171,7 @@ public ValidationMetadata ignore( boolean ignore ) { /** Sets the mask which the String form of the property must match. */ public ValidationMetadata mask( String mask ) { - _mask = Pattern.compile(mask); + _mask = Pattern.compile(mask); return this; } From 314275733a7abbe429bee97553b23773dd2e9ff2 Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Tue, 8 Dec 2020 17:35:42 +0100 Subject: [PATCH 2/9] added root binding to ValidateForm annotation --- .../DefaultActionBeanPropertyBinder.java | 10 ++++++- .../stripes/controller/DispatcherHelper.java | 4 +-- .../tag/BeanFirstPopulationStrategy.java | 18 ++++++++++- .../DefaultValidationMetadataProvider.java | 30 +++++++++++++++++-- .../stripes/validation/ValidateForm.java | 2 ++ .../validation/ValidationMetadata.java | 22 +++++++++++++- 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java index 6acf8c7d1..2c552686e 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java @@ -126,9 +126,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()) ) { diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java index b39775b5e..777ded158 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java @@ -517,11 +517,11 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception { .getValidationMetadata(beanClass); Map additionalForms = new HashMap<>(); for ( Map.Entry validationMetadataEntry : validationMetadata.entrySet() ) { - if ( validationMetadataEntry.getValue().getForm() == null ) { + if ( validationMetadataEntry.getValue().form() == null ) { continue; } - Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().getForm()); + Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().form()); form.setContext(context); FormValidation formValidation = new FormValidation(); diff --git a/stripes/src/main/java/net/sourceforge/stripes/tag/BeanFirstPopulationStrategy.java b/stripes/src/main/java/net/sourceforge/stripes/tag/BeanFirstPopulationStrategy.java index bf88cdac3..edbbc2a55 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/tag/BeanFirstPopulationStrategy.java +++ b/stripes/src/main/java/net/sourceforge/stripes/tag/BeanFirstPopulationStrategy.java @@ -14,12 +14,16 @@ */ package net.sourceforge.stripes.tag; +import java.util.Map; + import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.controller.ParameterName; import net.sourceforge.stripes.controller.StripesConstants; import net.sourceforge.stripes.exception.StripesJspException; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.bean.BeanUtil; import net.sourceforge.stripes.util.bean.ExpressionException; +import net.sourceforge.stripes.validation.ValidationMetadata; /** @@ -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 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()) ) { diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java index e2af51534..5e5abb0a0 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java @@ -269,10 +269,13 @@ protected Map loadForClass( Class beanType ) { if ( nestedAnnotation != null || formAnnotation != null ) { Validate[] validates; String[] additionalOns; + boolean rootBinding; if ( formAnnotation != null && formAnnotation.form() != Form.class ) { Class form = formAnnotation.form(); + rootBinding = formAnnotation.rootBinding(); additionalOns = formAnnotation.on(); + //noinspection unchecked Map formAnnotationInfoMap = getAnnotationInfoMap(form, Validate.class, ValidateNestedProperties.class); if ( formAnnotationInfoMap.size() != 1 ) { @@ -289,12 +292,13 @@ protected Map loadForClass( Class beanType ) { } } //noinspection unchecked - ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class>)form); + ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class>)form).rootBinding(rootBinding); for ( String additionalOn : additionalOns ) { validationMetadata.on(additionalOn); } meta.put(propertyName, validationMetadata); } else { + rootBinding = false; additionalOns = new String[0]; if ( nestedAnnotation != null ) { validates = nestedAnnotation.value(); @@ -305,15 +309,37 @@ protected Map loadForClass( Class beanType ) { for ( Validate validate : validates ) { if ( !"".equals(validate.field()) ) { + String shortName; + String bindingPrefix; + if ( rootBinding ) { + shortName = validate.field(); + bindingPrefix = propertyName + '.'; + } else { + shortName = null; + bindingPrefix = null; + } + String fullName = propertyName + '.' + validate.field(); if ( meta.containsKey(fullName) ) { - log.warn("More than one nestedAnnotation @Validate with same field name: " + validate.field() + " on property " + propertyName); + log.warn("More than one nestedAnnotation @Validate with same field name: " + fullName + " on property " + propertyName); } ValidationMetadata validationMetadata = new ValidationMetadata(fullName, validate); for ( String additionalOn : additionalOns ) { validationMetadata.on(additionalOn); } meta.put(fullName, validationMetadata); + + if ( shortName != null ) { + if ( meta.containsKey(shortName) ) { + log.warn("More than one nestedAnnotation @Validate with same field name: " + shortName + " on property " + propertyName); + } + ValidationMetadata shortNameValidationMetadata = new ValidationMetadata(shortName, validate); + shortNameValidationMetadata.rootBinding(true).bindingPrefix(bindingPrefix); + for ( String additionalOn : additionalOns ) { + shortNameValidationMetadata.on(additionalOn); + } + meta.put(shortName, shortNameValidationMetadata); + } } else { log.warn("Field name missing from nestedAnnotation @Validate: ", clazz, ", property ", propertyName); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java index 14afd5a19..e087eb800 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java @@ -39,4 +39,6 @@ String[] on() default {}; + boolean rootBinding() default false; + } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java index 5adf3a4c5..95005f2f6 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java @@ -53,6 +53,8 @@ public class ValidationMetadata { @SuppressWarnings("rawtypes") private Class _converter; private String _label; + private boolean _rootBinding; + private String _bindingPrefix; /** * Constructs a ValidationMetadata object for the specified property. Further constraints @@ -113,6 +115,15 @@ public ValidationMetadata( String property, Class> form ) { _form = form; } + public ValidationMetadata bindingPrefix( String bindingPrefix ) { + _bindingPrefix = bindingPrefix; + return this; + } + + public String bindingPrefix() { + return _bindingPrefix; + } + /** Sets the overridden TypeConveter to use to convert values. */ @SuppressWarnings("rawtypes") public ValidationMetadata converter( Class converter ) { @@ -145,7 +156,7 @@ public ValidationMetadata expression( String expression ) { /** Returns the overridden TypeConverter if there is one, or null. */ public String expression() { return _expression; } - public Class> getForm() { + public Class> form() { return _form; } @@ -257,6 +268,15 @@ public boolean requiredOn( String event ) { return _required && !_ignore && ((_on == null) || (_onIsPositive && _on.contains(event)) || (!_onIsPositive && !_on.contains(event))); } + public boolean rootBinding() { + return _rootBinding; + } + + public ValidationMetadata rootBinding( boolean rootBinding ) { + _rootBinding = rootBinding; + return this; + } + /** * Overidden toString() that only outputs the constraints that are specified by * the instance of validation metadata (i.e. omits nulls, defaults etc.) From c28249ebe9c2520291d11e4971b2148c45461d05 Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Thu, 17 Dec 2020 09:14:22 +0100 Subject: [PATCH 3/9] added form validation feature, added SingleBeanForm --- .../net/sourceforge/stripes/action/Form.java | 6 +- .../stripes/action/SingleBeanForm.java | 8 +++ .../stripes/controller/DispatcherHelper.java | 61 ++++++++----------- .../DefaultValidationMetadataProvider.java | 2 +- .../stripes/validation/FormValidation.java | 6 +- .../validation/ValidationMetadata.java | 12 +++- 6 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Form.java b/stripes/src/main/java/net/sourceforge/stripes/action/Form.java index eed9af632..381ee5022 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Form.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/Form.java @@ -1,9 +1,5 @@ package net.sourceforge.stripes.action; -public interface Form extends ActionBean { - - T getBean(); - - void setBean( T initial ); +public interface Form extends ActionBean { } diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java b/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java new file mode 100644 index 000000000..a99336529 --- /dev/null +++ b/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java @@ -0,0 +1,8 @@ +package net.sourceforge.stripes.action; + +public interface SingleBeanForm extends Form { + + T getBean(); + + void setBean( T initial ); +} diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java index 777ded158..1bac7e3be 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java @@ -15,6 +15,7 @@ package net.sourceforge.stripes.controller; import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; @@ -37,6 +38,7 @@ import net.sourceforge.stripes.action.DontValidate; import net.sourceforge.stripes.action.Form; import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.action.SingleBeanForm; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.exception.StripesServletException; import net.sourceforge.stripes.util.CollectionUtil; @@ -161,51 +163,24 @@ 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 forms = context.getForms(); for ( Map.Entry formEntry : forms.entrySet() ) { FormValidation formValidation = formEntry.getValue(); - //noinspection rawtypes Form form = formValidation.getForm(); if ( !CollectionUtil.applies(formValidation.getOn().toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) { continue; } - //noinspection unchecked - form.setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean)); - - Method[] formValidations = findCustomValidationMethods(form.getClass()); - for ( Method validation : formValidations ) { - 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(form, errors); - } else { - validation.invoke(form); - } - } + if ( form instanceof SingleBeanForm ) { + //noinspection unchecked,rawtypes + ((SingleBeanForm)form).setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean)); } + Method[] formValidations = findCustomValidationMethods(form.getClass()); + doCustomValidation(ctx, form, formValidations, alwaysInvokeValidate, errors); } fillInValidationErrors(ctx); @@ -521,7 +496,7 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception { continue; } - Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().form()); + Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().form()); form.setContext(context); FormValidation formValidation = new FormValidation(); @@ -605,4 +580,22 @@ public static void setPageContext( PageContext ctx ) { pageContextStash.set(ctx); } } + + private static void doCustomValidation( ExecutionContext ctx, ActionBean 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); + } + } + } + } } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java index 5e5abb0a0..1537f5f96 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java @@ -292,7 +292,7 @@ protected Map loadForClass( Class beanType ) { } } //noinspection unchecked - ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class>)form).rootBinding(rootBinding); + ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class

)form).rootBinding(rootBinding); for ( String additionalOn : additionalOns ) { validationMetadata.on(additionalOn); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java index 9d3ba3c57..4cef7df14 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java @@ -8,10 +8,10 @@ public class FormValidation { - private Form form; + private Form form; private Set on; - public Form getForm() { + public Form getForm() { return form; } @@ -22,7 +22,7 @@ public Set getOn() { return on; } - public void setForm( Form form ) { + public void setForm( Form form ) { this.form = form; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java index 95005f2f6..c02bf1de2 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java @@ -37,7 +37,8 @@ public class ValidationMetadata { private final String _property; - private Class> _form; + private Validate _validate; + private Class _form; private boolean _encrypted; private boolean _required; private boolean _trim; @@ -77,6 +78,7 @@ public ValidationMetadata( String property ) { public ValidationMetadata( String property, Validate validate ) { // Copy over all the simple values _property = property; + _validate = validate; encrypted(validate.encrypted()); required(validate.required()); trim(validate.trim()); @@ -110,7 +112,7 @@ public ValidationMetadata( String property, Validate validate ) { } } - public ValidationMetadata( String property, Class> form ) { + public ValidationMetadata( String property, Class form ) { _property = property; _form = form; } @@ -156,7 +158,7 @@ public ValidationMetadata expression( String expression ) { /** Returns the overridden TypeConverter if there is one, or null. */ public String expression() { return _expression; } - public Class> form() { + public Class form() { return _form; } @@ -302,5 +304,9 @@ public ValidationMetadata trim( boolean trim ) { /** Returns true if the field should be trimmed before validation or type conversion. */ public boolean trim() { return _trim; } + + public Validate validate() { + return _validate; + } } From 9641b5a4d59adbcd45a7d4902e81a11733d69d5c Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Fri, 8 Jan 2021 17:51:56 +0100 Subject: [PATCH 4/9] validating bean properties as forms --- .../net/sourceforge/stripes/action/Form.java | 5 - .../stripes/action/SingleBeanForm.java | 2 +- .../stripes/controller/DispatcherHelper.java | 53 +++++++-- .../bean/PropertyExpressionEvaluation.java | 12 ++- .../DefaultValidationMetadataProvider.java | 102 ++++++++++++++---- .../stripes/validation/FormValidation.java | 10 +- .../stripes/validation/ValidateForm.java | 6 +- .../validation/ValidationMetadata.java | 52 +++++---- 8 files changed, 172 insertions(+), 70 deletions(-) delete mode 100644 stripes/src/main/java/net/sourceforge/stripes/action/Form.java diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/Form.java b/stripes/src/main/java/net/sourceforge/stripes/action/Form.java deleted file mode 100644 index 381ee5022..000000000 --- a/stripes/src/main/java/net/sourceforge/stripes/action/Form.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.sourceforge.stripes.action; - -public interface Form extends ActionBean { - -} diff --git a/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java b/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java index a99336529..6fdc33d0d 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java +++ b/stripes/src/main/java/net/sourceforge/stripes/action/SingleBeanForm.java @@ -1,6 +1,6 @@ package net.sourceforge.stripes.action; -public interface SingleBeanForm extends Form { +public interface SingleBeanForm extends ActionBean { T getBean(); diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java index 1bac7e3be..2f7752792 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/DispatcherHelper.java @@ -25,6 +25,7 @@ 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; @@ -36,7 +37,6 @@ import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.DontBind; import net.sourceforge.stripes.action.DontValidate; -import net.sourceforge.stripes.action.Form; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.action.SingleBeanForm; import net.sourceforge.stripes.config.Configuration; @@ -46,6 +46,7 @@ import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.bean.BeanUtil; import net.sourceforge.stripes.validation.FormValidation; +import net.sourceforge.stripes.validation.ValidateForm; import net.sourceforge.stripes.validation.ValidationError; import net.sourceforge.stripes.validation.ValidationErrorHandler; import net.sourceforge.stripes.validation.ValidationErrors; @@ -168,15 +169,42 @@ public Resolution intercept( ExecutionContext context ) throws Exception { Map forms = context.getForms(); for ( Map.Entry formEntry : forms.entrySet() ) { FormValidation formValidation = formEntry.getValue(); - Form form = formValidation.getForm(); + SingleBeanForm form = formValidation.getForm(); - if ( !CollectionUtil.applies(formValidation.getOn().toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) { + Set on = formValidation.getOn(); + if ( on != null && !CollectionUtil.applies(on.toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) { continue; } - if ( form instanceof SingleBeanForm ) { - //noinspection unchecked,rawtypes - ((SingleBeanForm)form).setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean)); + //noinspection unchecked,rawtypes + ((SingleBeanForm)form).setBean(BeanUtil.getPropertyValue(formEntry.getKey(), bean)); + + Method[] formValidations = findCustomValidationMethods(form.getClass()); + doCustomValidation(ctx, form, formValidations, alwaysInvokeValidate, errors); + } + + Map validationMetadata = StripesFilter.getConfiguration() + .getValidationMetadataProvider() + .getValidationMetadata(bean.getClass()); + + for ( Map.Entry validationMetadataEntry : validationMetadata.entrySet() ) { + ValidationMetadata formValidation = validationMetadataEntry.getValue(); + Class> 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 on = formValidation.on(); + if ( on != null && !CollectionUtil.applies(on.toArray(new String[0]), ctx.getActionBeanContext().getEventName()) ) { + continue; } Method[] formValidations = findCustomValidationMethods(form.getClass()); @@ -285,7 +313,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 type ) throws Exception { + public static Method[] findCustomValidationMethods( Class type ) throws Exception { Method[] validations = null; WeakReference ref = customValidations.get(type); if ( ref != null ) { @@ -490,13 +518,18 @@ public Resolution intercept( ExecutionContext ctx ) throws Exception { Map validationMetadata = StripesFilter.getConfiguration() .getValidationMetadataProvider() .getValidationMetadata(beanClass); + Map additionalForms = new HashMap<>(); for ( Map.Entry validationMetadataEntry : validationMetadata.entrySet() ) { - if ( validationMetadataEntry.getValue().form() == null ) { + Class> formClass = validationMetadataEntry.getValue().form(); + if ( formClass == null ) { + continue; + } + if ( formClass == ValidateForm.AnyForm.class ) { continue; } - Form form = StripesFilter.getConfiguration().getObjectFactory().newInstance(validationMetadataEntry.getValue().form()); + SingleBeanForm form = StripesFilter.getConfiguration().getObjectFactory().newInstance(formClass); form.setContext(context); FormValidation formValidation = new FormValidation(); @@ -581,7 +614,7 @@ public static void setPageContext( PageContext ctx ) { } } - private static void doCustomValidation( ExecutionContext ctx, ActionBean bean, Method[] validations, boolean alwaysInvokeValidate, ValidationErrors errors ) + 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); diff --git a/stripes/src/main/java/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java b/stripes/src/main/java/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java index 85e3affbc..69ac6bc76 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java +++ b/stripes/src/main/java/net/sourceforge/stripes/util/bean/PropertyExpressionEvaluation.java @@ -29,6 +29,9 @@ import java.util.List; import java.util.Map; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.controller.ExecutionContext; import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.util.ReflectUtil; @@ -60,8 +63,8 @@ public class PropertyExpressionEvaluation { * @param bean a non-null bean against which to evaluate the expression */ public PropertyExpressionEvaluation( PropertyExpression expression, Object bean ) { - _expression = expression; - _bean = bean; + _expression = expression; + _bean = bean; for ( Node node = expression.getRootNode(); node != null; node = node.getNext() ) { NodeEvaluation evaluation = new NodeEvaluation(this, node); @@ -686,6 +689,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); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java index 1537f5f96..48b41c653 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java @@ -30,7 +30,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import net.sourceforge.stripes.action.Form; +import net.sourceforge.stripes.action.SingleBeanForm; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.controller.ParameterName; import net.sourceforge.stripes.exception.StripesRuntimeException; @@ -268,16 +268,21 @@ protected Map loadForClass( Class beanType ) { // add all sub-properties referenced in @ValidateNestedProperties if ( nestedAnnotation != null || formAnnotation != null ) { Validate[] validates; - String[] additionalOns; + String[] ons; boolean rootBinding; - if ( formAnnotation != null && formAnnotation.form() != Form.class ) { - Class form = formAnnotation.form(); + if ( formAnnotation != null ) { + if ( formAnnotation.form() == ValidateForm.AnyForm.class ) { + continue; + } + + Class> form = formAnnotation.form(); rootBinding = formAnnotation.rootBinding(); - additionalOns = formAnnotation.on(); + ons = formAnnotation.on(); //noinspection unchecked - Map formAnnotationInfoMap = getAnnotationInfoMap(form, Validate.class, ValidateNestedProperties.class); + Map formAnnotationInfoMap = getAnnotationInfoMap(form, Validate.class, ValidateNestedProperties.class, + ValidateForm.class); if ( formAnnotationInfoMap.size() != 1 ) { log.warn("Form used for proerty ", propertyName, " defines ", formAnnotationInfoMap.size(), " validations. Expecting exactly 1 for property 'bean'."); @@ -291,24 +296,20 @@ protected Map loadForClass( Class beanType ) { validates = validatedProperty.getAnnotation(ValidateNestedProperties.class).value(); } } - //noinspection unchecked - ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, (Class)form).rootBinding(rootBinding); - for ( String additionalOn : additionalOns ) { - validationMetadata.on(additionalOn); - } + + ValidationMetadata validationMetadata = new ValidationMetadata(propertyName, form); + validationMetadata.rootBinding(rootBinding).on(ons); meta.put(propertyName, validationMetadata); } else { rootBinding = false; - additionalOns = new String[0]; - if ( nestedAnnotation != null ) { - validates = nestedAnnotation.value(); - } else { - validates = new Validate[0]; - } + ons = null; + validates = nestedAnnotation.value(); } for ( Validate validate : validates ) { - if ( !"".equals(validate.field()) ) { + if ( "".equals(validate.field()) ) { + log.warn("Field name missing from nestedAnnotation @Validate: ", clazz, ", property ", propertyName); + } else { String shortName; String bindingPrefix; if ( rootBinding ) { @@ -324,8 +325,8 @@ protected Map loadForClass( Class beanType ) { log.warn("More than one nestedAnnotation @Validate with same field name: " + fullName + " on property " + propertyName); } ValidationMetadata validationMetadata = new ValidationMetadata(fullName, validate); - for ( String additionalOn : additionalOns ) { - validationMetadata.on(additionalOn); + if ( ons != null ) { + validationMetadata.on(ons); } meta.put(fullName, validationMetadata); @@ -335,18 +336,73 @@ protected Map loadForClass( Class beanType ) { } ValidationMetadata shortNameValidationMetadata = new ValidationMetadata(shortName, validate); shortNameValidationMetadata.rootBinding(true).bindingPrefix(bindingPrefix); - for ( String additionalOn : additionalOns ) { + for ( String additionalOn : ons ) { shortNameValidationMetadata.on(additionalOn); } meta.put(shortName, shortNameValidationMetadata); } - } else { - log.warn("Field name missing from nestedAnnotation @Validate: ", clazz, ", property ", propertyName); } } } } + @SuppressWarnings("unchecked") Map formsAnnotationInfoMap = getAnnotationInfoMap(beanType, ValidateForm.class); + for ( String propertyName : formsAnnotationInfoMap.keySet() ) { + AnnotationInfo annotationInfo = formsAnnotationInfoMap.get(propertyName); + + // get the @Validate and/or @ValidateNestedProperties + ValidateForm formAnnotation = annotationInfo.getAnnotation(ValidateForm.class); + Class clazz = annotationInfo.getTargetClass(); + + if ( formAnnotation.form() != ValidateForm.AnyForm.class ) { + continue; + } + + PropertyDescriptor propertyDescriptor = ReflectUtil.getPropertyDescriptor(clazz, propertyName); + Class form = propertyDescriptor.getPropertyType(); + + boolean rootBinding = formAnnotation.rootBinding(); + String[] ons = formAnnotation.on(); + + ValidationMetadata formValidationMetadata = new ValidationMetadata(propertyName, ValidateForm.AnyForm.class); + formValidationMetadata.rootBinding(rootBinding).on(ons); + meta.put(propertyName, formValidationMetadata); + + Map formValidationMetadataMap = loadForClass(form); + for ( String formPropertyName : formValidationMetadataMap.keySet() ) { + ValidationMetadata formPropertyValidationMetadata = formValidationMetadataMap.get(formPropertyName); + + String shortName; + String bindingPrefix; + if ( rootBinding ) { + shortName = formPropertyName; + bindingPrefix = propertyName + '.'; + } else { + shortName = null; + bindingPrefix = null; + } + + String fullName = propertyName + '.' + formPropertyName; + if ( meta.containsKey(fullName) ) { + log.warn("More than one nestedAnnotation @Validate with same field name: " + fullName + " on property " + propertyName); + } + + ValidationMetadata validationMetadata = ValidationMetadata.copy(fullName, formPropertyValidationMetadata); + validationMetadata.on(ons); + meta.put(fullName, validationMetadata); + + if ( shortName != null ) { + if ( meta.containsKey(shortName) ) { + log.warn("More than one nestedAnnotation @Validate with same field name: " + shortName + " on property " + propertyName); + } + ValidationMetadata shortNameValidationMetadata = ValidationMetadata.copy(shortName, formPropertyValidationMetadata); + shortNameValidationMetadata.rootBinding(true).bindingPrefix(bindingPrefix).on(ons); + meta.put(shortName, shortNameValidationMetadata); + } + } + + } + return Collections.unmodifiableMap(meta); } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java index 4cef7df14..620132878 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/FormValidation.java @@ -3,15 +3,15 @@ import java.util.Collections; import java.util.Set; -import net.sourceforge.stripes.action.Form; +import net.sourceforge.stripes.action.SingleBeanForm; public class FormValidation { - private Form form; - private Set on; + private SingleBeanForm form; + private Set on; - public Form getForm() { + public SingleBeanForm getForm() { return form; } @@ -22,7 +22,7 @@ public Set getOn() { return on; } - public void setForm( Form form ) { + public void setForm( SingleBeanForm form ) { this.form = form; } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java index e087eb800..3c4a8bba6 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidateForm.java @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import net.sourceforge.stripes.action.Form; +import net.sourceforge.stripes.action.SingleBeanForm; /** @@ -35,10 +35,12 @@ @Documented public @interface ValidateForm { - Class form() default Form.class; + Class> form() default AnyForm.class; String[] on() default {}; boolean rootBinding() default false; + interface AnyForm extends SingleBeanForm {} + } diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java index c02bf1de2..e99166afe 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/ValidationMetadata.java @@ -18,7 +18,7 @@ import java.util.Set; import java.util.regex.Pattern; -import net.sourceforge.stripes.action.Form; +import net.sourceforge.stripes.action.SingleBeanForm; /** @@ -36,26 +36,34 @@ */ public class ValidationMetadata { - private final String _property; - private Validate _validate; - private Class _form; - private boolean _encrypted; - private boolean _required; - private boolean _trim; - private Set _on; - private boolean _onIsPositive; - private boolean _ignore; - private Integer _minlength; - private Integer _maxlength; - private Double _minvalue; - private Double _maxvalue; - private Pattern _mask; - private String _expression; + public static ValidationMetadata copy( String property, ValidationMetadata original ) { + if ( original.validate() != null ) { + return new ValidationMetadata(property, original.validate()); + } else { + return new ValidationMetadata(property, original.form()); + } + } + + private final String _property; + private Validate _validate; + private Class> _form; + private boolean _encrypted; + private boolean _required; + private boolean _trim; + private Set _on; + private boolean _onIsPositive; + private boolean _ignore; + private Integer _minlength; + private Integer _maxlength; + private Double _minvalue; + private Double _maxvalue; + private Pattern _mask; + private String _expression; @SuppressWarnings("rawtypes") - private Class _converter; - private String _label; - private boolean _rootBinding; - private String _bindingPrefix; + private Class _converter; + private String _label; + private boolean _rootBinding; + private String _bindingPrefix; /** * Constructs a ValidationMetadata object for the specified property. Further constraints @@ -112,7 +120,7 @@ public ValidationMetadata( String property, Validate validate ) { } } - public ValidationMetadata( String property, Class form ) { + public ValidationMetadata( String property, Class> form ) { _property = property; _form = form; } @@ -158,7 +166,7 @@ public ValidationMetadata expression( String expression ) { /** Returns the overridden TypeConverter if there is one, or null. */ public String expression() { return _expression; } - public Class form() { + public Class> form() { return _form; } From 760dcd35593e8470986e170e84d0b9f5a5708694 Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Mon, 1 Mar 2021 17:21:16 +0100 Subject: [PATCH 5/9] testing form validation feature --- .../DefaultValidationMetadataProvider.java | 7 +- .../stripes/validation/BusinessValue.java | 35 +++ .../stripes/validation/BusinessValueForm.java | 64 +++++ .../BusinessValueFormAsWrapper.java | 63 +++++ ...FormFormAsValueWrapperRootBindingTest.java | 220 ++++++++++++++++++ .../ValidationFormFormAsValueWrapperTest.java | 220 ++++++++++++++++++ ...tionFormSingleBeanFormRootBindingTest.java | 217 +++++++++++++++++ .../ValidationFormSingleBeanFormTest.java | 217 +++++++++++++++++ 8 files changed, 1041 insertions(+), 2 deletions(-) create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValue.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueForm.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueFormAsWrapper.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperRootBindingTest.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperTest.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormRootBindingTest.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormTest.java diff --git a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java index 48b41c653..8741a7cb1 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java +++ b/stripes/src/main/java/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java @@ -324,7 +324,9 @@ protected Map loadForClass( Class beanType ) { if ( meta.containsKey(fullName) ) { log.warn("More than one nestedAnnotation @Validate with same field name: " + fullName + " on property " + propertyName); } - ValidationMetadata validationMetadata = new ValidationMetadata(fullName, validate); + + boolean ignoreFullNameProperty = shortName != null; + ValidationMetadata validationMetadata = new ValidationMetadata(fullName, validate).ignore(ignoreFullNameProperty); if ( ons != null ) { validationMetadata.on(ons); } @@ -387,7 +389,8 @@ protected Map loadForClass( Class beanType ) { log.warn("More than one nestedAnnotation @Validate with same field name: " + fullName + " on property " + propertyName); } - ValidationMetadata validationMetadata = ValidationMetadata.copy(fullName, formPropertyValidationMetadata); + boolean ignoreFullNameProperty = shortName != null; + ValidationMetadata validationMetadata = ValidationMetadata.copy(fullName, formPropertyValidationMetadata).ignore(ignoreFullNameProperty); validationMetadata.on(ons); meta.put(fullName, validationMetadata); diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValue.java b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValue.java new file mode 100644 index 000000000..62d5c4365 --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValue.java @@ -0,0 +1,35 @@ +package net.sourceforge.stripes.validation; + +public class BusinessValue { + + private int numberZero; + private int numberOne; + private int numberTwo; + + @SuppressWarnings("unused") + public int getNumberOne() { + return numberOne; + } + + @SuppressWarnings("unused") + public int getNumberTwo() { + return numberTwo; + } + + @SuppressWarnings("unused") + public int getNumberZero() { + return numberZero; + } + + public void setNumberOne( int numberOne ) { + this.numberOne = numberOne; + } + + public void setNumberTwo( int numberTwo ) { + this.numberTwo = numberTwo; + } + + public void setNumberZero( int numberZero ) { + this.numberZero = numberZero; + } +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueForm.java b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueForm.java new file mode 100644 index 000000000..dd13e933c --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueForm.java @@ -0,0 +1,64 @@ +package net.sourceforge.stripes.validation; + +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.SingleBeanForm; + + +public class BusinessValueForm implements SingleBeanForm { + + static ThreadLocal counter = ThreadLocal.withInitial(() -> 1); + static ThreadLocal validateAlwaysRan = ThreadLocal.withInitial(() -> 0); + static ThreadLocal validateOneRan = ThreadLocal.withInitial(() -> 0); + static ThreadLocal validateTwoRan = ThreadLocal.withInitial(() -> 0); + + private ActionBeanContext _context; + private BusinessValue _businessObject; + + @ValidateNestedProperties({ // + @Validate(field = "numberZero", required = true), // + @Validate(field = "numberOne", required = true, minvalue = 0), // + @Validate(field = "numberTwo") // + }) + @Override + public BusinessValue getBean() { + return _businessObject; + } + + @Override + public ActionBeanContext getContext() { + return _context; + } + + @Override + public void setBean( BusinessValue businessObject ) { + _businessObject = businessObject; + } + + @Override + public void setContext( ActionBeanContext context ) { + _context = context; + } + + @ValidationMethod(priority = 0) + @SuppressWarnings("DefaultAnnotationParam") + public void validateAlways( ValidationErrors errors ) { + if ( errors == null ) { + throw new RuntimeException("errors must not be null"); + } + validateAlwaysRan.set(counter.get()); + counter.set(counter.get() + 1); + } + + @ValidationMethod(priority = 1, when = ValidationState.NO_ERRORS) + public void validateOne() { + validateOneRan.set(counter.get()); + counter.set(counter.get() + 1); + } + + @ValidationMethod(priority = 1, when = ValidationState.ALWAYS) + public void validateTwo( ValidationErrors errors ) { + validateTwoRan.set(counter.get()); + counter.set(counter.get() + 1); + } + +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueFormAsWrapper.java b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueFormAsWrapper.java new file mode 100644 index 000000000..af2880bf4 --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/BusinessValueFormAsWrapper.java @@ -0,0 +1,63 @@ +package net.sourceforge.stripes.validation; + +public class BusinessValueFormAsWrapper { + + static ThreadLocal counter = ThreadLocal.withInitial(() -> 1); + static ThreadLocal validateAlwaysRan = ThreadLocal.withInitial(() -> 0); + static ThreadLocal validateOneRan = ThreadLocal.withInitial(() -> 0); + static ThreadLocal validateTwoRan = ThreadLocal.withInitial(() -> 0); + + private final BusinessValue businessValue = new BusinessValue(); + + @SuppressWarnings("unused") + public int getNumberOne() { + return businessValue.getNumberOne(); + } + + @SuppressWarnings("unused") + public int getNumberTwo() { + return businessValue.getNumberTwo(); + } + + @SuppressWarnings("unused") + public int getNumberZero() { + return businessValue.getNumberZero(); + } + + @Validate(required = true, minvalue = 0) + public void setNumberOne( int numberOne ) { + businessValue.setNumberOne(numberOne); + } + + @Validate() + public void setNumberTwo( int numberTwo ) { + businessValue.setNumberTwo(numberTwo); + } + + @Validate(required = true) + public void setNumberZero( int numberZero ) { + businessValue.setNumberZero(numberZero); + } + + @ValidationMethod(priority = 0) + @SuppressWarnings("DefaultAnnotationParam") + public void validateAlways( ValidationErrors errors ) { + if ( errors == null ) { + throw new RuntimeException("errors must not be null"); + } + validateAlwaysRan.set(counter.get()); + counter.set(counter.get() + 1); + } + + @ValidationMethod(priority = 1, when = ValidationState.NO_ERRORS) + public void validateOne() { + validateOneRan.set(counter.get()); + counter.set(counter.get() + 1); + } + + @ValidationMethod(priority = 1, when = ValidationState.ALWAYS) + public void validateTwo( ValidationErrors errors ) { + validateTwoRan.set(counter.get()); + counter.set(counter.get() + 1); + } +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperRootBindingTest.java b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperRootBindingTest.java new file mode 100644 index 000000000..3ff2ed763 --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperRootBindingTest.java @@ -0,0 +1,220 @@ +package net.sourceforge.stripes.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.sourceforge.stripes.FilterEnabledTestBase; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.DefaultHandler; +import net.sourceforge.stripes.action.HandlesEvent; +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.action.UrlBinding; +import net.sourceforge.stripes.mock.MockRoundtrip; + + +/** + * Test out various aspects of the validation subsystem in Stripes with regard to optional + * application, ordering and flow control. Each of the individual test methods has javadoc + * explaining why the expected results are as they are. + * + * @author Olaf Stracke + */ +@UrlBinding("/test/ValidationFormFormAsValueWrapperRootBindingTest.action") +public class ValidationFormFormAsValueWrapperRootBindingTest extends FilterEnabledTestBase implements ActionBean { + + private ActionBeanContext _context; + private BusinessValueFormAsWrapper _businessValue1; + + @BeforeEach + public void before() { + BusinessValueFormAsWrapper.counter.set(1); + BusinessValueFormAsWrapper.validateAlwaysRan.set(0); + BusinessValueFormAsWrapper.validateOneRan.set(0); + BusinessValueFormAsWrapper.validateTwoRan.set(0); + } + + @ValidateForm(on = "eventOne", rootBinding = true) + public BusinessValueFormAsWrapper getBusinessValue1() { + if ( _businessValue1 == null ) { + _businessValue1 = new BusinessValueFormAsWrapper(); + } + return _businessValue1; + } + + @Override + public ActionBeanContext getContext() { return _context; } + + @DefaultHandler + @HandlesEvent("eventOne") + public Resolution one() { return null; } + + @Override + public void setContext( ActionBeanContext context ) { _context = context;} + + /** + * Almost identical to testEventOneWithNoErrors except that we invoke the 'default' event. + * Tests to make sure that event-specific validations are still applied correctly when the + * event name isn't present in the request. + */ + @Test + public void testEventOneAsDefault() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", "101"); + trip.execute(); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Number one is a required field also for this event, so we supply it. This event should + * cause both validateAlways and validateOne to run. + */ + @Test + public void testEventOneNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", "101"); + + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + @Test + public void testEventOneWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", ""); // required field always + trip.addParameter("numberTwo", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getContext().getValidationErrors()).hasSize(2); + } + + /** + * Tests that a required field error is raised this time for numberOne which is only + * required for this event. Again this single error should prevent both validateAlways + * and validateOne from running. + */ + @Test + public void testEventOneWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + /** + * Straightforward test for event two that makes sure it's validations run. This time + * numberTwo should be required (and is supplied) and validateAlways and validateTwo should + * run but not validateOne. + */ + @Test + public void testEventTwoNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberTwo", "102"); + + trip.execute("eventTwo"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberTwo()).isEqualTo(102); + assertThat(test.getContext().getValidationErrors().size()).isEqualTo(0); + } + + /** + * Tests that validateTwo is run event though there are errors and valiateAlways did not run, + * because validateTwo is marked to run always. + */ + @Test + public void testEventTwoWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", ""); // required field always + trip.addParameter("numberTwo", ""); // required field for event one + trip.execute("eventTwo"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).hasSize(0); + } + + /** + * numberZero is the only required field for eventZero, so there should be no validation + * errors generated. The only validation method that should be run is validateAlways() because + * the others are tied to specific events. + */ + @Test + public void testEventZeroNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "99"); + trip.execute("eventZero"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Generates an error by providing an invalid value for numberOne (which has a minimum + * value of 0). Validations other than required are still applied even though that @Validate + * has a on="one". The single validaiton error should prevent validateAlways() and + * validateOne from running. + */ + @Test + public void testEventZeroWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "99"); + trip.addParameter("numberOne", "-100"); + trip.execute("eventZero"); + + ValidationFormFormAsValueWrapperRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(99); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + @HandlesEvent("eventTwo") + @SuppressWarnings("unused") + public Resolution two() { return null; } + + @HandlesEvent("eventZero") + @SuppressWarnings("unused") + public Resolution zero() { return null; } + +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperTest.java b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperTest.java new file mode 100644 index 000000000..10649733b --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormFormAsValueWrapperTest.java @@ -0,0 +1,220 @@ +package net.sourceforge.stripes.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.sourceforge.stripes.FilterEnabledTestBase; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.DefaultHandler; +import net.sourceforge.stripes.action.HandlesEvent; +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.action.UrlBinding; +import net.sourceforge.stripes.mock.MockRoundtrip; + + +/** + * Test out various aspects of the validation subsystem in Stripes with regard to optional + * application, ordering and flow control. Each of the individual test methods has javadoc + * explaining why the expected results are as they are. + * + * @author Olaf Stracke + */ +@UrlBinding("/test/ValidationFormFormAsValueWrapperTest.action") +public class ValidationFormFormAsValueWrapperTest extends FilterEnabledTestBase implements ActionBean { + + private ActionBeanContext _context; + private BusinessValueFormAsWrapper _businessValue1; + + @BeforeEach + public void before() { + BusinessValueFormAsWrapper.counter.set(1); + BusinessValueFormAsWrapper.validateAlwaysRan.set(0); + BusinessValueFormAsWrapper.validateOneRan.set(0); + BusinessValueFormAsWrapper.validateTwoRan.set(0); + } + + @ValidateForm(on = "eventOne") + public BusinessValueFormAsWrapper getBusinessValue1() { + if ( _businessValue1 == null ) { + _businessValue1 = new BusinessValueFormAsWrapper(); + } + return _businessValue1; + } + + @Override + public ActionBeanContext getContext() { return _context; } + + @DefaultHandler + @HandlesEvent("eventOne") + public Resolution one() { return null; } + + @Override + public void setContext( ActionBeanContext context ) { _context = context;} + + /** + * Almost identical to testEventOneWithNoErrors except that we invoke the 'default' event. + * Tests to make sure that event-specific validations are still applied correctly when the + * event name isn't present in the request. + */ + @Test + public void testEventOneAsDefault() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", "101"); + trip.execute(); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Number one is a required field also for this event, so we supply it. This event should + * cause both validateAlways and validateOne to run. + */ + @Test + public void testEventOneNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", "101"); + + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + @Test + public void testEventOneWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", ""); // required field always + trip.addParameter("businessValue1.numberTwo", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getContext().getValidationErrors()).hasSize(2); + } + + /** + * Tests that a required field error is raised this time for numberOne which is only + * required for this event. Again this single error should prevent both validateAlways + * and validateOne from running. + */ + @Test + public void testEventOneWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + /** + * Straightforward test for event two that makes sure it's validations run. This time + * numberTwo should be required (and is supplied) and validateAlways and validateTwo should + * run but not validateOne. + */ + @Test + public void testEventTwoNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberTwo", "102"); + + trip.execute("eventTwo"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test._businessValue1.getNumberZero()).isEqualTo(100); + assertThat(test._businessValue1.getNumberTwo()).isEqualTo(102); + assertThat(test.getContext().getValidationErrors().size()).isEqualTo(0); + } + + /** + * Tests that validateTwo is run event though there are errors and valiateAlways did not run, + * because validateTwo is marked to run always. + */ + @Test + public void testEventTwoWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", ""); // required field always + trip.addParameter("businessValue1.numberTwo", ""); // required field for event one + trip.execute("eventTwo"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).hasSize(0); + } + + /** + * numberZero is the only required field for eventZero, so there should be no validation + * errors generated. The only validation method that should be run is validateAlways() because + * the others are tied to specific events. + */ + @Test + public void testEventZeroNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "99"); + trip.execute("eventZero"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Generates an error by providing an invalid value for numberOne (which has a minimum + * value of 0). Validations other than required are still applied even though that @Validate + * has a on="one". The single validaiton error should prevent validateAlways() and + * validateOne from running. + */ + @Test + public void testEventZeroWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "99"); + trip.addParameter("businessValue1.numberOne", "-100"); + trip.execute("eventZero"); + + ValidationFormFormAsValueWrapperTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueFormAsWrapper.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueFormAsWrapper.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(99); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + @HandlesEvent("eventTwo") + @SuppressWarnings("unused") + public Resolution two() { return null; } + + @HandlesEvent("eventZero") + @SuppressWarnings("unused") + public Resolution zero() { return null; } + +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormRootBindingTest.java b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormRootBindingTest.java new file mode 100644 index 000000000..de3bd801e --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormRootBindingTest.java @@ -0,0 +1,217 @@ +package net.sourceforge.stripes.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.sourceforge.stripes.FilterEnabledTestBase; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.DefaultHandler; +import net.sourceforge.stripes.action.HandlesEvent; +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.action.UrlBinding; +import net.sourceforge.stripes.mock.MockRoundtrip; + + +/** + * Test out various aspects of the validation subsystem in Stripes with regard to optional + * application, ordering and flow control. Each of the individual test methods has javadoc + * explaining why the expected results are as they are. + * + * @author Olaf Stracke + */ +@UrlBinding("/test/ValidationFormSingleBeanRootBindingTest.action") +public class ValidationFormSingleBeanFormRootBindingTest extends FilterEnabledTestBase implements ActionBean { + + private ActionBeanContext _context; + private final BusinessValue _businessValue1 = new BusinessValue(); + + @BeforeEach + public void before() { + BusinessValueForm.counter.set(1); + BusinessValueForm.validateAlwaysRan.set(0); + BusinessValueForm.validateOneRan.set(0); + BusinessValueForm.validateTwoRan.set(0); + } + + @ValidateForm(form = BusinessValueForm.class, on = "eventOne", rootBinding = true) + public BusinessValue getBusinessValue1() { + return _businessValue1; + } + + @Override + public ActionBeanContext getContext() { return _context; } + + @DefaultHandler + @HandlesEvent("eventOne") + public Resolution one() { return null; } + + @Override + public void setContext( ActionBeanContext context ) { _context = context;} + + /** + * Almost identical to testEventOneWithNoErrors except that we invoke the 'default' event. + * Tests to make sure that event-specific validations are still applied correctly when the + * event name isn't present in the request. + */ + @Test + public void testEventOneAsDefault() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", "101"); + trip.execute(); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Number one is a required field also for this event, so we supply it. This event should + * cause both validateAlways and validateOne to run. + */ + @Test + public void testEventOneNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", "101"); + + trip.execute("eventOne"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + @Test + public void testEventOneWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", ""); // required field always + trip.addParameter("numberTwo", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getContext().getValidationErrors()).hasSize(2); + } + + /** + * Tests that a required field error is raised this time for numberOne which is only + * required for this event. Again this single error should prevent both validateAlways + * and validateOne from running. + */ + @Test + public void testEventOneWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberOne", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + /** + * Straightforward test for event two that makes sure it's validations run. This time + * numberTwo should be required (and is supplied) and validateAlways and validateTwo should + * run but not validateOne. + */ + @Test + public void testEventTwoNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "100"); + trip.addParameter("numberTwo", "102"); + + trip.execute("eventTwo"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberTwo()).isEqualTo(102); + assertThat(test.getContext().getValidationErrors().size()).isEqualTo(0); + } + + /** + * Tests that validateTwo is run event though there are errors and valiateAlways did not run, + * because validateTwo is marked to run always. + */ + @Test + public void testEventTwoWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", ""); // required field always + trip.addParameter("numberTwo", ""); // required field for event one + trip.execute("eventTwo"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).hasSize(0); + } + + /** + * numberZero is the only required field for eventZero, so there should be no validation + * errors generated. The only validation method that should be run is validateAlways() because + * the others are tied to specific events. + */ + @Test + public void testEventZeroNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "99"); + trip.execute("eventZero"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Generates an error by providing an invalid value for numberOne (which has a minimum + * value of 0). Validations other than required are still applied even though that @Validate + * has a on="one". The single validaiton error should prevent validateAlways() and + * validateOne from running. + */ + @Test + public void testEventZeroWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("numberZero", "99"); + trip.addParameter("numberOne", "-100"); + trip.execute("eventZero"); + + ValidationFormSingleBeanFormRootBindingTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(99); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + @HandlesEvent("eventTwo") + @SuppressWarnings("unused") + public Resolution two() { return null; } + + @HandlesEvent("eventZero") + @SuppressWarnings("unused") + public Resolution zero() { return null; } + +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormTest.java b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormTest.java new file mode 100644 index 000000000..4b31573d2 --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/validation/ValidationFormSingleBeanFormTest.java @@ -0,0 +1,217 @@ +package net.sourceforge.stripes.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.sourceforge.stripes.FilterEnabledTestBase; +import net.sourceforge.stripes.action.ActionBean; +import net.sourceforge.stripes.action.ActionBeanContext; +import net.sourceforge.stripes.action.DefaultHandler; +import net.sourceforge.stripes.action.HandlesEvent; +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.action.UrlBinding; +import net.sourceforge.stripes.mock.MockRoundtrip; + + +/** + * Test out various aspects of the validation subsystem in Stripes with regard to optional + * application, ordering and flow control. Each of the individual test methods has javadoc + * explaining why the expected results are as they are. + * + * @author Olaf Stracke + */ +@UrlBinding("/test/ValidationFormSingleBeanFormTest.action") +public class ValidationFormSingleBeanFormTest extends FilterEnabledTestBase implements ActionBean { + + private ActionBeanContext _context; + private final BusinessValue _businessValue1 = new BusinessValue(); + + @BeforeEach + public void before() { + BusinessValueForm.counter.set(1); + BusinessValueForm.validateAlwaysRan.set(0); + BusinessValueForm.validateOneRan.set(0); + BusinessValueForm.validateTwoRan.set(0); + } + + @ValidateForm(form = BusinessValueForm.class, on = "eventOne") + public BusinessValue getBusinessValue1() { + return _businessValue1; + } + + @Override + public ActionBeanContext getContext() { return _context; } + + @DefaultHandler + @HandlesEvent("eventOne") + public Resolution one() { return null; } + + @Override + public void setContext( ActionBeanContext context ) { _context = context;} + + /** + * Almost identical to testEventOneWithNoErrors except that we invoke the 'default' event. + * Tests to make sure that event-specific validations are still applied correctly when the + * event name isn't present in the request. + */ + @Test + public void testEventOneAsDefault() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", "101"); + trip.execute(); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Number one is a required field also for this event, so we supply it. This event should + * cause both validateAlways and validateOne to run. + */ + @Test + public void testEventOneNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", "101"); + + trip.execute("eventOne"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(1); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(2); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(3); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getBusinessValue1().getNumberOne()).isEqualTo(101); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + @Test + public void testEventOneWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", ""); // required field always + trip.addParameter("businessValue1.numberTwo", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getContext().getValidationErrors()).hasSize(2); + } + + /** + * Tests that a required field error is raised this time for numberOne which is only + * required for this event. Again this single error should prevent both validateAlways + * and validateOne from running. + */ + @Test + public void testEventOneWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberOne", ""); // required field for event one + trip.execute("eventOne"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(1); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(100); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + /** + * Straightforward test for event two that makes sure it's validations run. This time + * numberTwo should be required (and is supplied) and validateAlways and validateTwo should + * run but not validateOne. + */ + @Test + public void testEventTwoNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "100"); + trip.addParameter("businessValue1.numberTwo", "102"); + + trip.execute("eventTwo"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test._businessValue1.getNumberZero()).isEqualTo(100); + assertThat(test._businessValue1.getNumberTwo()).isEqualTo(102); + assertThat(test.getContext().getValidationErrors().size()).isEqualTo(0); + } + + /** + * Tests that validateTwo is run event though there are errors and valiateAlways did not run, + * because validateTwo is marked to run always. + */ + @Test + public void testEventTwoWithEmptyFields() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", ""); // required field always + trip.addParameter("businessValue1.numberTwo", ""); // required field for event one + trip.execute("eventTwo"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).hasSize(0); + } + + /** + * numberZero is the only required field for eventZero, so there should be no validation + * errors generated. The only validation method that should be run is validateAlways() because + * the others are tied to specific events. + */ + @Test + public void testEventZeroNoErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "99"); + trip.execute("eventZero"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getContext().getValidationErrors()).isEmpty(); + } + + /** + * Generates an error by providing an invalid value for numberOne (which has a minimum + * value of 0). Validations other than required are still applied even though that @Validate + * has a on="one". The single validaiton error should prevent validateAlways() and + * validateOne from running. + */ + @Test + public void testEventZeroWithErrors() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); + trip.addParameter("businessValue1.numberZero", "99"); + trip.addParameter("businessValue1.numberOne", "-100"); + trip.execute("eventZero"); + + ValidationFormSingleBeanFormTest test = trip.getActionBean(getClass()); + assertThat(BusinessValueForm.validateAlwaysRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateOneRan.get().intValue()).isEqualTo(0); + assertThat(BusinessValueForm.validateTwoRan.get().intValue()).isEqualTo(0); + assertThat(test.getBusinessValue1().getNumberZero()).isEqualTo(99); + assertThat(test.getContext().getValidationErrors()).hasSize(1); + } + + @HandlesEvent("eventTwo") + @SuppressWarnings("unused") + public Resolution two() { return null; } + + @HandlesEvent("eventZero") + @SuppressWarnings("unused") + public Resolution zero() { return null; } + +} From c8d7b3ba1177545eed378d2f71e91fc2dd586c5a Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Fri, 18 Jun 2021 11:00:55 +0200 Subject: [PATCH 6/9] fixed packages and imports after merging main branch --- .../web/action/SingleBeanForm.java | 2 +- .../web/controller/DispatcherHelper.java | 5 +++++ .../web/controller/ExecutionContext.java | 10 +++++----- .../bean/PropertyExpressionEvaluation.java | 6 +++--- .../DefaultValidationMetadataProvider.java | 2 +- .../web/validation/FormValidation.java | 4 ++-- .../web/validation/ValidateForm.java | 4 ++-- .../web/validation/ValidationMetadata.java | 10 +++++----- .../web/validation/BusinessValue.java | 2 +- .../web/validation/BusinessValueForm.java | 6 +++--- .../BusinessValueFormAsWrapper.java | 4 ++-- ...FormFormAsValueWrapperRootBindingTest.java | 19 +++++++++---------- .../ValidationFormFormAsValueWrapperTest.java | 19 +++++++++---------- ...tionFormSingleBeanFormRootBindingTest.java | 19 +++++++++---------- .../ValidationFormSingleBeanFormTest.java | 19 +++++++++---------- 15 files changed, 66 insertions(+), 65 deletions(-) diff --git a/stripes-web/src/main/java/org/stripesframework/web/action/SingleBeanForm.java b/stripes-web/src/main/java/org/stripesframework/web/action/SingleBeanForm.java index 6fdc33d0d..6bab711fd 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/action/SingleBeanForm.java +++ b/stripes-web/src/main/java/org/stripesframework/web/action/SingleBeanForm.java @@ -1,4 +1,4 @@ -package net.sourceforge.stripes.action; +package org.stripesframework.web.action; public interface SingleBeanForm extends ActionBean { diff --git a/stripes-web/src/main/java/org/stripesframework/web/controller/DispatcherHelper.java b/stripes-web/src/main/java/org/stripesframework/web/controller/DispatcherHelper.java index 0902b761e..d9baaf8c0 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/controller/DispatcherHelper.java +++ b/stripes-web/src/main/java/org/stripesframework/web/controller/DispatcherHelper.java @@ -37,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; diff --git a/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java b/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java index d557de932..7b89faa44 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java +++ b/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java @@ -23,7 +23,7 @@ import org.stripesframework.web.action.ActionBeanContext; import org.stripesframework.web.action.Resolution; import org.stripesframework.web.util.Log; -import net.sourceforge.stripes.validation.FormValidation; +import org.stripesframework.web.validation.FormValidation; /** @@ -138,7 +138,7 @@ 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 forms ) { @@ -160,14 +160,14 @@ public void setInterceptors( Collection 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; } /** @@ -181,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 diff --git a/stripes-web/src/main/java/org/stripesframework/web/util/bean/PropertyExpressionEvaluation.java b/stripes-web/src/main/java/org/stripesframework/web/util/bean/PropertyExpressionEvaluation.java index 7ecfb920b..5072d1fb5 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/util/bean/PropertyExpressionEvaluation.java +++ b/stripes-web/src/main/java/org/stripesframework/web/util/bean/PropertyExpressionEvaluation.java @@ -29,9 +29,9 @@ import java.util.List; import java.util.Map; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.controller.ExecutionContext; +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; diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/DefaultValidationMetadataProvider.java b/stripes-web/src/main/java/org/stripesframework/web/validation/DefaultValidationMetadataProvider.java index 995fc6559..556afe480 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/DefaultValidationMetadataProvider.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/DefaultValidationMetadataProvider.java @@ -30,7 +30,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import net.sourceforge.stripes.action.SingleBeanForm; +import org.stripesframework.web.action.SingleBeanForm; import org.stripesframework.web.config.Configuration; import org.stripesframework.web.controller.ParameterName; import org.stripesframework.web.exception.StripesRuntimeException; diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java b/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java index 620132878..3f9949ea1 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java @@ -1,9 +1,9 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import java.util.Collections; import java.util.Set; -import net.sourceforge.stripes.action.SingleBeanForm; +import org.stripesframework.web.action.SingleBeanForm; public class FormValidation { diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java index 3c4a8bba6..c9cbc2171 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import net.sourceforge.stripes.action.SingleBeanForm; +import org.stripesframework.web.action.SingleBeanForm; /** diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidationMetadata.java b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidationMetadata.java index 63ce9dd4e..740b6650a 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidationMetadata.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidationMetadata.java @@ -18,7 +18,7 @@ import java.util.Set; import java.util.regex.Pattern; -import net.sourceforge.stripes.action.SingleBeanForm; +import org.stripesframework.web.action.SingleBeanForm; /** @@ -60,10 +60,10 @@ public static ValidationMetadata copy( String property, ValidationMetadata origi private Pattern _mask; @SuppressWarnings("rawtypes") - private Class _converter; - private String _label; - private boolean _rootBinding; - private String _bindingPrefix; + private Class _converter; + private String _label; + private boolean _rootBinding; + private String _bindingPrefix; /** * Constructs a ValidationMetadata object for the specified property. Further constraints diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValue.java b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValue.java index 62d5c4365..25316d5c3 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValue.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValue.java @@ -1,4 +1,4 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; public class BusinessValue { diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueForm.java b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueForm.java index dd13e933c..4ea75c851 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueForm.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueForm.java @@ -1,7 +1,7 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.action.SingleBeanForm; +import org.stripesframework.web.action.ActionBeanContext; +import org.stripesframework.web.action.SingleBeanForm; public class BusinessValueForm implements SingleBeanForm { diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueFormAsWrapper.java b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueFormAsWrapper.java index af2880bf4..d6e08ff82 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueFormAsWrapper.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/BusinessValueFormAsWrapper.java @@ -1,4 +1,4 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; public class BusinessValueFormAsWrapper { @@ -39,8 +39,8 @@ public void setNumberZero( int numberZero ) { businessValue.setNumberZero(numberZero); } - @ValidationMethod(priority = 0) @SuppressWarnings("DefaultAnnotationParam") + @ValidationMethod(priority = 0) public void validateAlways( ValidationErrors errors ) { if ( errors == null ) { throw new RuntimeException("errors must not be null"); diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java index 3ff2ed763..ac174427a 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java @@ -1,18 +1,17 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import net.sourceforge.stripes.FilterEnabledTestBase; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.action.DefaultHandler; -import net.sourceforge.stripes.action.HandlesEvent; -import net.sourceforge.stripes.action.Resolution; -import net.sourceforge.stripes.action.UrlBinding; -import net.sourceforge.stripes.mock.MockRoundtrip; +import org.stripesframework.web.FilterEnabledTestBase; +import org.stripesframework.web.action.ActionBean; +import org.stripesframework.web.action.ActionBeanContext; +import org.stripesframework.web.action.DefaultHandler; +import org.stripesframework.web.action.HandlesEvent; +import org.stripesframework.web.action.Resolution; +import org.stripesframework.web.action.UrlBinding; +import org.stripesframework.web.mock.MockRoundtrip; /** diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java index 10649733b..4835ac4f6 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java @@ -1,18 +1,17 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import net.sourceforge.stripes.FilterEnabledTestBase; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.action.DefaultHandler; -import net.sourceforge.stripes.action.HandlesEvent; -import net.sourceforge.stripes.action.Resolution; -import net.sourceforge.stripes.action.UrlBinding; -import net.sourceforge.stripes.mock.MockRoundtrip; +import org.stripesframework.web.FilterEnabledTestBase; +import org.stripesframework.web.action.ActionBean; +import org.stripesframework.web.action.ActionBeanContext; +import org.stripesframework.web.action.DefaultHandler; +import org.stripesframework.web.action.HandlesEvent; +import org.stripesframework.web.action.Resolution; +import org.stripesframework.web.action.UrlBinding; +import org.stripesframework.web.mock.MockRoundtrip; /** diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java index de3bd801e..e6cbb2d61 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java @@ -1,18 +1,17 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import net.sourceforge.stripes.FilterEnabledTestBase; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.action.DefaultHandler; -import net.sourceforge.stripes.action.HandlesEvent; -import net.sourceforge.stripes.action.Resolution; -import net.sourceforge.stripes.action.UrlBinding; -import net.sourceforge.stripes.mock.MockRoundtrip; +import org.stripesframework.web.FilterEnabledTestBase; +import org.stripesframework.web.action.ActionBean; +import org.stripesframework.web.action.ActionBeanContext; +import org.stripesframework.web.action.DefaultHandler; +import org.stripesframework.web.action.HandlesEvent; +import org.stripesframework.web.action.Resolution; +import org.stripesframework.web.action.UrlBinding; +import org.stripesframework.web.mock.MockRoundtrip; /** diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java index 4b31573d2..d09546be9 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java @@ -1,18 +1,17 @@ -package net.sourceforge.stripes.validation; +package org.stripesframework.web.validation; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import net.sourceforge.stripes.FilterEnabledTestBase; -import net.sourceforge.stripes.action.ActionBean; -import net.sourceforge.stripes.action.ActionBeanContext; -import net.sourceforge.stripes.action.DefaultHandler; -import net.sourceforge.stripes.action.HandlesEvent; -import net.sourceforge.stripes.action.Resolution; -import net.sourceforge.stripes.action.UrlBinding; -import net.sourceforge.stripes.mock.MockRoundtrip; +import org.stripesframework.web.FilterEnabledTestBase; +import org.stripesframework.web.action.ActionBean; +import org.stripesframework.web.action.ActionBeanContext; +import org.stripesframework.web.action.DefaultHandler; +import org.stripesframework.web.action.HandlesEvent; +import org.stripesframework.web.action.Resolution; +import org.stripesframework.web.action.UrlBinding; +import org.stripesframework.web.mock.MockRoundtrip; /** From 72ca6609c59353baefce27c69fcd576fa7e12899 Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Fri, 18 Jun 2021 13:42:38 +0200 Subject: [PATCH 7/9] added test docs --- ...lidationFormFormAsValueWrapperRootBindingTest.java | 11 +++++++++-- .../ValidationFormFormAsValueWrapperTest.java | 6 +++++- .../ValidationFormSingleBeanFormRootBindingTest.java | 11 +++++++++-- .../validation/ValidationFormSingleBeanFormTest.java | 7 ++++++- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java index ac174427a..8ffcb9242 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperRootBindingTest.java @@ -17,8 +17,15 @@ /** * Test out various aspects of the validation subsystem in Stripes with regard to optional * application, ordering and flow control. Each of the individual test methods has javadoc - * explaining why the expected results are as they are. - * + * explaining why the expected results are as they are.
+ *
+ * This test handles the usecase for filling up business value objects (domain model, {@link BusinessValue}) + * by using a wrapper form ({@link BusinessValueFormAsWrapper}). The wrapper form defines all + * validations for validating the input parameters.
+ *
+ * This test tests the rootBinding option. This allows to bind a property like "businessValue1.numberOne" + * by using the parameter "numberOne" (the properties of the given beans are directly bound to the action).
+ *
* @author Olaf Stracke */ @UrlBinding("/test/ValidationFormFormAsValueWrapperRootBindingTest.action") diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java index 4835ac4f6..54b778647 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormFormAsValueWrapperTest.java @@ -17,7 +17,11 @@ /** * Test out various aspects of the validation subsystem in Stripes with regard to optional * application, ordering and flow control. Each of the individual test methods has javadoc - * explaining why the expected results are as they are. + * explaining why the expected results are as they are.
+ *
+ * This test handles the usecase for filling up business value objects (domain model, {@link BusinessValue}) + * by using a wrapper form ({@link BusinessValueFormAsWrapper}). The wrapper form defines all + * validations for validating the input parameters.
* * @author Olaf Stracke */ diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java index e6cbb2d61..5836119df 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormRootBindingTest.java @@ -17,8 +17,15 @@ /** * Test out various aspects of the validation subsystem in Stripes with regard to optional * application, ordering and flow control. Each of the individual test methods has javadoc - * explaining why the expected results are as they are. - * + * explaining why the expected results are as they are.
+ *
+ * This test handles the usecase for filling up business value objects (domain model, {@link BusinessValue}) + * by using a spezilized form ({@link BusinessValueForm}). The form defines all + * validations for validating the input parameters.
+ *
+ * This test tests the rootBinding option. This allows to bind a property like "businessValue1.numberOne" + * by using the parameter "numberOne" (the properties of the given beans are directly bound to the action).
+ *
* @author Olaf Stracke */ @UrlBinding("/test/ValidationFormSingleBeanRootBindingTest.action") diff --git a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java index d09546be9..8589bb821 100644 --- a/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java +++ b/stripes-web/src/test/java/org/stripesframework/web/validation/ValidationFormSingleBeanFormTest.java @@ -17,7 +17,12 @@ /** * Test out various aspects of the validation subsystem in Stripes with regard to optional * application, ordering and flow control. Each of the individual test methods has javadoc - * explaining why the expected results are as they are. + * explaining why the expected results are as they are.
+ *
+ * This test handles the usecase for filling up business value objects (domain model, {@link BusinessValue}) + * by using a spezilized form ({@link BusinessValueForm}). The form defines all + * validations for validating the input parameters.
+ *
* * @author Olaf Stracke */ From af05a3021bed7c35195b12af42aa55599171ac41 Mon Sep 17 00:00:00 2001 From: Olaf Stracke Date: Fri, 18 Jun 2021 13:53:02 +0200 Subject: [PATCH 8/9] bugfix --- .../jsp/tag/BeanFirstPopulationStrategy.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stripes-jsp/src/main/java/org/stripesframework/jsp/tag/BeanFirstPopulationStrategy.java b/stripes-jsp/src/main/java/org/stripesframework/jsp/tag/BeanFirstPopulationStrategy.java index c8b67ed36..a4efb9697 100644 --- a/stripes-jsp/src/main/java/org/stripesframework/jsp/tag/BeanFirstPopulationStrategy.java +++ b/stripes-jsp/src/main/java/org/stripesframework/jsp/tag/BeanFirstPopulationStrategy.java @@ -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; /** From 2daf857bf5709ba7e5080e162112b360affc2004 Mon Sep 17 00:00:00 2001 From: casid Date: Fri, 18 Jun 2021 15:09:51 +0200 Subject: [PATCH 9/9] Code formatting --- .../web/controller/ExecutionContext.java | 6 +++--- .../web/validation/FormValidation.java | 14 +++++++------- .../web/validation/ValidateForm.java | 7 ------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java b/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java index 7b89faa44..d2fe084a4 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java +++ b/stripes-web/src/main/java/org/stripesframework/web/controller/ExecutionContext.java @@ -58,7 +58,7 @@ public static ExecutionContext currentContext() { private Resolution _resolution; private LifecycleStage _lifecycleStage; private boolean _resolutionFromHandler = false; - private Map forms; + private Map _forms; /** * Retrieves the ActionBean instance that is associated with the current request. Available @@ -79,7 +79,7 @@ public ActionBeanContext getActionBeanContext() { } public Map getForms() { - return forms; + return _forms; } /** @@ -142,7 +142,7 @@ public void setActionBeanContext( ActionBeanContext actionBeanContext ) { } public void setForms( Map forms ) { - this.forms = forms; + _forms = forms; } /** Sets the handler method that will be invoked to process the current request. */ diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java b/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java index 3f9949ea1..fb310d098 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/FormValidation.java @@ -8,25 +8,25 @@ public class FormValidation { - private SingleBeanForm form; - private Set on; + private SingleBeanForm _form; + private Set _on; public SingleBeanForm getForm() { - return form; + return _form; } public Set getOn() { - if ( on == null ) { + if ( _on == null ) { return Collections.emptySet(); } - return on; + return _on; } public void setForm( SingleBeanForm form ) { - this.form = form; + _form = form; } public void setOn( Set on ) { - this.on = on; + _on = on; } } diff --git a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java index c9cbc2171..ea1e47208 100644 --- a/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java +++ b/stripes-web/src/main/java/org/stripesframework/web/validation/ValidateForm.java @@ -23,13 +23,6 @@ import org.stripesframework.web.action.SingleBeanForm; -/** - * Annotation used to capture the validation needs of nested properties within an ActionBean. It - * contains a simple array of the Validate annotations. Each Validate annotation must have its - * field property set to the name of the field within the annotated property that is to be validated. - * - * @author Tim Fennell - */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) @Documented