From 5e4b38598420d83b53592860c04d9fd4164c720f Mon Sep 17 00:00:00 2001 From: Hedju Hor Date: Tue, 9 Jan 2018 12:58:42 +0100 Subject: [PATCH] support Constraints Group in BeanValidation-Plugin --- .../constraints/ValidateGroup.java | 50 +++++++ .../BeanValidationInterceptor.java | 22 ++- .../BeanValidationInterceptorTest.java | 138 +++++++++++++++++- .../actions/ValidateGroupAction.java | 70 +++++++++ .../struts/beanvalidation/models/Address.java | 5 +- .../struts/beanvalidation/models/Person.java | 36 ++++- .../test/resources/bean-validation-test.xml | 10 +- 7 files changed, 314 insertions(+), 17 deletions(-) create mode 100644 plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/constraints/ValidateGroup.java create mode 100644 plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/actions/ValidateGroupAction.java diff --git a/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/constraints/ValidateGroup.java b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/constraints/ValidateGroup.java new file mode 100644 index 000000000..4ca0b37cb --- /dev/null +++ b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/constraints/ValidateGroup.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts.beanvalidation.constraints; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Validation with Group Constrains on execution method (Action) + *

+ * Example, Validate with on special group + *

+ *
+ *
+ *  {@literal @}ValidateGroup(CarChecks.class)
+ *  {@literal @}Action...
+ * 
+ *

+ *

+ * Example, Validate with severals special group + *

+ *
+ *  {@literal @}ValidateGroup(Default.class, CarChecks.class, DriverChecks.class)
+ *  {@literal @}Action...
+ * 
+ */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidateGroup { + + Class[] value() default {}; +} \ No newline at end of file diff --git a/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java index 12629c901..bdbca6a2f 100644 --- a/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java +++ b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java @@ -27,16 +27,18 @@ import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; import com.opensymphony.xwork2.validator.DelegatingValidatorContext; import com.opensymphony.xwork2.validator.ValidatorContext; import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts.beanvalidation.constraints.ValidateGroup; import org.apache.struts.beanvalidation.validation.constant.ValidatorConstants; import org.apache.struts2.interceptor.validation.SkipValidation; import javax.validation.ConstraintViolation; import javax.validation.Validator; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Set; /** @@ -95,36 +97,42 @@ public class BeanValidationInterceptor extends MethodFilterInterceptor { if (LOG.isDebugEnabled()) { LOG.debug("Validating [{}/{}] with method [{}]", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName(), methodName); } + Class[] validationGroup = getValidationGroups(action, methodName); if (null == MethodUtils.getAnnotation(getActionMethod(action.getClass(), methodName), SkipValidation.class, true, true)) { // performing bean validation on action - performBeanValidation(action, validator); + performBeanValidation(action, validator, validationGroup); } return invocation.invoke(); } - protected void performBeanValidation(Object action, Validator validator) { + protected Class[] getValidationGroups(Object action, String methodName) throws NoSuchMethodException { + ValidateGroup validateGroup = MethodUtils.getAnnotation(getActionMethod(action.getClass(), methodName), ValidateGroup.class, true, true); + return validateGroup == null ? new Class[]{} : validateGroup.value(); + } - LOG.trace("Initiating bean validation.."); + protected void performBeanValidation(Object action, Validator validator, Class[] groups) { + + LOG.trace("Initiating bean validation.. with groups [{}]", Arrays.toString(groups)); Set> constraintViolations; if (action instanceof ModelDriven) { LOG.trace("Performing validation on model.."); Object model = (Object)((ModelDriven) action).getModel(); - constraintViolations = validator.validate(model); + constraintViolations = validator.validate(model, groups); } else { LOG.trace("Performing validation on action.."); - constraintViolations = validator.validate(action); + constraintViolations = validator.validate(action, groups); } addBeanValidationErrors(constraintViolations, action); } @SuppressWarnings("nls") - private void addBeanValidationErrors(Set> constraintViolations, Object action) { + protected void addBeanValidationErrors(Set> constraintViolations, Object action) { if (constraintViolations != null) { ValidatorContext validatorContext = new DelegatingValidatorContext(action, textProviderFactory); for (ConstraintViolation constraintViolation : constraintViolations) { diff --git a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/BeanValidationInterceptorTest.java b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/BeanValidationInterceptorTest.java index 944083c60..6b45432ef 100644 --- a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/BeanValidationInterceptorTest.java +++ b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/BeanValidationInterceptorTest.java @@ -19,14 +19,13 @@ package org.apache.struts.beanvalidation; import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.interceptor.ValidationAware; import com.opensymphony.xwork2.XWorkTestCase; import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; - +import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.struts.beanvalidation.actions.FieldAction; -import org.apache.struts.beanvalidation.actions.FieldActionDoExecute; import org.apache.struts.beanvalidation.actions.FieldMatchAction; import org.apache.struts.beanvalidation.actions.ModelDrivenAction; +import org.apache.struts.beanvalidation.actions.ValidateGroupAction; import java.util.Collection; import java.util.List; @@ -147,9 +146,140 @@ public class BeanValidationInterceptorTest extends XWorkTestCase { assertEquals(2, actionErrors.size()); } + + public void testValidationGroupActionStandard() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionStandard"); + ValidateGroupAction action = (ValidateGroupAction) baseActionProxy.getAction(); + action.getModel().setName(null); + action.getModel().setEmail(null); + action.getModel().getAddress().setStreet(null); + baseActionProxy.execute(); + assertEquals("every properties not valid", 3, ((ValidationAware) baseActionProxy.getAction()).getFieldErrors().size()); + } + + + public void testValidationGroupActionDefault() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionDefault"); + ValidateGroupAction action = (ValidateGroupAction) baseActionProxy.getAction(); + action.getModel().setName(null); + action.getModel().setEmail(null); + action.getModel().getAddress().setStreet(null); + + baseActionProxy.execute(); + assertEquals("every properties not valid", 3, ((ValidationAware) baseActionProxy.getAction()).getFieldErrors().size()); + } + + public void testValidationGroupActionNameChecks() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionNameChecks"); + baseActionProxy.execute(); + Map> fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(1, fieldErrors.size()); + assertEquals("check name-property @NotNull", "nameNotNull", fieldErrors.get("name").get(0)); + + baseActionProxy = getValidateGroupAction("actionNameChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("a"); + baseActionProxy.execute(); + fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(1, fieldErrors.size()); + assertEquals("check name-property @Size", "nameSize", fieldErrors.get("name").get(0)); + + baseActionProxy = getValidateGroupAction("actionNameChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("aName"); + baseActionProxy.execute(); + assertTrue("name-property valid", ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors().isEmpty()); + } + + + public void testValidationGroupActionStreetChecks() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionStreetChecks"); + baseActionProxy.execute(); + Map> fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(1, fieldErrors.size()); + assertEquals("street-property @NotNull", "streetNotNull", fieldErrors.get("address.street").get(0)); + + baseActionProxy = getValidateGroupAction("actionStreetChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().getAddress().setStreet("a"); + baseActionProxy.execute(); + fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(1, fieldErrors.size()); + assertEquals("street-property @Size", "streetSize", fieldErrors.get("address.street").get(0)); + + baseActionProxy = getValidateGroupAction("actionStreetChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().getAddress().setStreet("aStreet"); + baseActionProxy.execute(); + assertTrue("street-property valid", ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors().isEmpty()); + } + + + public void testValidationGroupActionNameAndStreetChecks() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionNameAndStreetChecks"); + baseActionProxy.execute(); + Map> fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(2, fieldErrors.size()); + assertTrue("name-property @NotNull", fieldErrors.containsKey("name")); + assertTrue("street-property @NotNull", fieldErrors.containsKey("address.street")); + + baseActionProxy = getValidateGroupAction("actionNameAndStreetChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("aName"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().getAddress().setStreet("aStreet"); + baseActionProxy.execute(); + assertTrue("name and street-property valid", ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors().isEmpty()); + } + + public void testValidationGroupActionMultiGroupsChecks() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionMultiGroupsChecks"); + baseActionProxy.execute(); + Map> fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(2, fieldErrors.size()); + assertTrue("name-property @NotNull", fieldErrors.containsKey("name")); + assertTrue("firstName-property @NotBlank", fieldErrors.containsKey("firstName")); + + baseActionProxy = getValidateGroupAction("actionMultiGroupsChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("aName"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setFirstName("aFirstName"); + baseActionProxy.execute(); + assertTrue("name and firstName-property valid", ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors().isEmpty()); + } + + public void testValidationGroupActionLongNameChecks() throws Exception { + ActionProxy baseActionProxy = getValidateGroupAction("actionLongNameChecks"); + baseActionProxy.execute(); + Map> fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(3, fieldErrors.size()); + assertTrue("name-property @NotNull", fieldErrors.containsKey("name")); + assertTrue("email-property @NotNull", fieldErrors.containsKey("email")); + assertTrue("street-property @NotNull", fieldErrors.containsKey("address.street")); + + baseActionProxy = getValidateGroupAction("actionLongNameChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("toShortName"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setEmail("actionLongNameChecks@mail.org"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().getAddress().setStreet("aStreet"); + baseActionProxy.execute(); + fieldErrors = ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors(); + assertEquals(1, fieldErrors.size()); + assertEquals("name-property @Size", "nameSize20", fieldErrors.get("name").get(0)); + + + baseActionProxy = getValidateGroupAction("actionLongNameChecks"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setName("this_is_a_really_long_Name"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().setEmail("actionLongNameChecks@mail.org"); + ((ValidateGroupAction) baseActionProxy.getAction()).getModel().getAddress().setStreet("aStreet"); + baseActionProxy.execute(); + assertTrue("every properties not valid", ((ValidateGroupAction) baseActionProxy.getAction()).getFieldErrors().isEmpty()); + } + + private ActionProxy getValidateGroupAction(String methodName) { + ActionProxy baseActionProxy = actionProxyFactory.createActionProxy("bean-validation", "validateGroupActions", methodName, null); + ValidateGroupAction action = (ValidateGroupAction) baseActionProxy.getAction(); + action.getModel().setName(null); + action.getModel().setEmail(null); + action.getModel().getAddress().setStreet(null); + return baseActionProxy; + } + @Override protected void setUp() throws Exception { super.setUp(); loadConfigurationProviders(new XmlConfigurationProvider("bean-validation-test.xml")); } -} +} \ No newline at end of file diff --git a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/actions/ValidateGroupAction.java b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/actions/ValidateGroupAction.java new file mode 100644 index 000000000..a2107a918 --- /dev/null +++ b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/actions/ValidateGroupAction.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts.beanvalidation.actions; + +import com.opensymphony.xwork2.ActionSupport; +import com.opensymphony.xwork2.ModelDriven; +import org.apache.struts.beanvalidation.constraints.ValidateGroup; +import org.apache.struts.beanvalidation.models.Person; + +import javax.validation.Valid; + +public class ValidateGroupAction extends ActionSupport implements ModelDriven { + + @Valid + private Person model = new Person(); + + public Person getModel() { + return model; + } + + public String actionStandard() { + return SUCCESS; + } + + @ValidateGroup + public String actionDefault() { + return SUCCESS; + } + + @ValidateGroup(Person.NameChecks.class) + public String actionNameChecks() { + return SUCCESS; + } + + @ValidateGroup(Person.StreetChecks.class) + public String actionStreetChecks() { + return SUCCESS; + } + + @ValidateGroup(Person.NameAndStreetChecks.class) + public String actionNameAndStreetChecks() { + return SUCCESS; + } + + @ValidateGroup({Person.NameChecks.class, Person.FirstNameChecks.class}) + public String actionMultiGroupsChecks() { + return SUCCESS; + } + + @ValidateGroup({Person.LongNameChecks.class}) + public String actionLongNameChecks() { + return SUCCESS; + } +} \ No newline at end of file diff --git a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Address.java b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Address.java index 284f4bceb..eddd1d004 100644 --- a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Address.java +++ b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Address.java @@ -20,11 +20,12 @@ package org.apache.struts.beanvalidation.models; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import javax.validation.groups.Default; public class Address { - @NotNull(message = "streetNotNull") - @Size(min = 3, max = 64, message = "streetSize") + @NotNull(message = "streetNotNull", groups = {Default.class, Person.StreetChecks.class, Person.NameAndStreetChecks.class}) + @Size(min = 3, max = 64, message = "streetSize", groups = {Default.class, Person.StreetChecks.class, Person.NameAndStreetChecks.class}) private String street; public void setStreet(String street) { diff --git a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Person.java b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Person.java index 7cc650d3b..be6b9cc1f 100644 --- a/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Person.java +++ b/plugins/bean-validation/src/test/java/org/apache/struts/beanvalidation/models/Person.java @@ -19,17 +19,41 @@ package org.apache.struts.beanvalidation.models; import org.hibernate.validator.constraints.Email; +import org.hibernate.validator.constraints.NotBlank; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import javax.validation.groups.Default; public class Person { - @NotNull(message = "nameNotNull") - @Size(min = 2, max = 64, message = "nameSize") + public interface NameChecks { + } + + public interface FirstNameChecks { + } + + public interface StreetChecks { + } + + public interface NameAndStreetChecks extends NameChecks, StreetChecks { + } + + public interface LongNameChecks extends Default { + } + + @NotNull(message = "nameNotNull", groups = {Default.class, NameChecks.class, NameAndStreetChecks.class}) + @Size.List({ + @Size(min = 4, max = 64, message = "nameSize", groups = {Default.class, NameChecks.class, NameAndStreetChecks.class}), + @Size(min = 20, max = 64, message = "nameSize20", groups = {LongNameChecks.class}) + + }) private String name; + @NotBlank(message = "firstNameNotBlank", groups = FirstNameChecks.class) + private String firstName; + @NotNull(message = "emailNotNull") @Email(message = "emailNotValid") private String email; @@ -53,6 +77,14 @@ public class Person { return name; } + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public void setAddress(Address address) { this.address = address; } diff --git a/plugins/bean-validation/src/test/resources/bean-validation-test.xml b/plugins/bean-validation/src/test/resources/bean-validation-test.xml index 950f08536..508d9a690 100644 --- a/plugins/bean-validation/src/test/resources/bean-validation-test.xml +++ b/plugins/bean-validation/src/test/resources/bean-validation-test.xml @@ -20,8 +20,8 @@ */ --> @@ -65,5 +65,11 @@ + + + + actionStandard, actionDefault, actionNameChecks, actionStreetChecks, actionNameAndStreetChecks, actionMultiGroupsChecks, actionLongNameChecks + +