Created
September 26, 2012 19:42
-
-
Save bmchild/3790115 to your computer and use it in GitHub Desktop.
AllFieldsOrNone SpringMVC 3 JSR-303 Validator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// package & imports | |
@AllFieldsOrNone(fields = {"stringOne", "stringTwo"} ) | |
public class MyBean { | |
private String stringOne; | |
private String stringTwo; | |
//...getters & setters | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
*/ | |
package com.bmchild.validation.constraints; | |
import static java.lang.annotation.ElementType.ANNOTATION_TYPE; | |
import static java.lang.annotation.ElementType.CONSTRUCTOR; | |
import static java.lang.annotation.ElementType.FIELD; | |
import static java.lang.annotation.ElementType.TYPE; | |
import static java.lang.annotation.ElementType.METHOD; | |
import static java.lang.annotation.ElementType.PARAMETER; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import javax.validation.Constraint; | |
import javax.validation.Payload; | |
/** | |
* Validator to make sure the specified fields are all populated or that none are populated. | |
* | |
* @author bchild | |
* | |
*/ | |
@Target({ METHOD, TYPE, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) | |
@Retention(RUNTIME) | |
@Documented | |
@Constraint(validatedBy = { AllFieldsOrNoneConstraintValidator.class }) | |
public @interface AllFieldsOrNone { | |
String message() default "must be all populated or all empty"; | |
Class<?>[] groups() default {}; | |
Class<? extends Payload>[] payload() default {}; | |
/** | |
* Array of fields that need to be all populated or all empty. | |
* <br /><br /> | |
* Reflection will be used to get the value from the object during runtime. | |
* @return fields | |
*/ | |
String[] fields(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
*/ | |
package com.bmchild.validation.constraints; | |
import java.lang.reflect.Method; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.List; | |
import javax.validation.ConstraintValidator; | |
import javax.validation.ConstraintValidatorContext; | |
import org.apache.commons.collections.CollectionUtils; | |
import org.apache.commons.lang.StringUtils; | |
import org.apache.log4j.Logger; | |
import com.bmchild.common.validation.ConstraintValidatorUtils; | |
/** | |
* @author bchild | |
* | |
*/ | |
public class AllFieldsOrNoneConstraintValidator implements | |
ConstraintValidator<AllFieldsOrNone, Object> { | |
private static final String VALIDATE_ERROR = "fields must not be empty"; | |
private static final Logger LOGGER = Logger.getLogger(AllFieldsOrNoneConstraintValidator.class); | |
private List<String> fields; | |
private String errorMessage; | |
/* (non-Javadoc) | |
* @see javax.validation.ConstraintValidator#initialize(java.lang.annotation.Annotation) | |
*/ | |
@Override | |
public void initialize(AllFieldsOrNone constraintAnnotation) { | |
validateFields(constraintAnnotation.fields()); | |
this.fields = Arrays.asList(constraintAnnotation.fields()); | |
this.errorMessage = constraintAnnotation.message(); | |
} | |
/* (non-Javadoc) | |
* @see javax.validation.ConstraintValidator#isValid(java.lang.Object, javax.validation.ConstraintValidatorContext) | |
*/ | |
@Override | |
public boolean isValid(Object value, ConstraintValidatorContext context) { | |
boolean isValid = true; | |
Class<?> clazz = value.getClass(); | |
Boolean lastValueIsNull = null; | |
for(String fieldName : fields) { | |
try { | |
Method getter = getGetter(fieldName, clazz); | |
Object fieldValue = getter.invoke(value); | |
Boolean isNull = checkEmptyValue(fieldValue); | |
if(lastValueIsNull != null && !lastValueIsNull.equals(isNull)) { | |
isValid = false; | |
break; | |
} | |
lastValueIsNull = isNull; | |
} catch (Exception e) { | |
this.errorMessage = String.format("Error validating %s: %s", fieldName, e.getMessage()); | |
isValid = false; | |
LOGGER.error(this.errorMessage, e); | |
} | |
} | |
// Add validation errors | |
if(!isValid) { | |
for(String fieldName : fields) { | |
ConstraintValidatorUtils.addConstraintViolation(context, errorMessage, fieldName); | |
} | |
} | |
return isValid; | |
} | |
/** | |
* @param fieldValue | |
* @return | |
*/ | |
private Boolean checkEmptyValue(Object fieldValue) { | |
Boolean isEmpty = false; | |
if(fieldValue instanceof String) { | |
isEmpty = StringUtils.isEmpty((String)fieldValue); | |
} else if (fieldValue instanceof Collection<?>) { | |
isEmpty = CollectionUtils.isEmpty((Collection<?>)fieldValue); | |
} else { | |
isEmpty = (null == fieldValue); | |
} | |
return isEmpty; | |
} | |
/** | |
* @param fieldName | |
* @param clazz | |
* @return | |
* @throws NoSuchMethodException | |
* @throws SecurityException | |
*/ | |
private Method getGetter(String fieldName, Class<?> clazz) throws SecurityException, NoSuchMethodException { | |
String getterName = createGetterName(fieldName); | |
return clazz.getMethod(getterName); | |
} | |
/** | |
* @param fieldName | |
* @return | |
*/ | |
private String createGetterName(String fieldName) { | |
String firstLetter = fieldName.substring(0, 1).toUpperCase(); | |
String getterName = "get" + firstLetter + fieldName.substring(1); | |
return getterName; | |
} | |
/** | |
* @param fieldArray | |
*/ | |
private void validateFields(String[] fieldArray) { | |
if(fieldArray == null || fieldArray.length == 0) { | |
throw new IllegalArgumentException(VALIDATE_ERROR); | |
} | |
for(String field : fieldArray) { | |
if(StringUtils.isEmpty(field)) { | |
throw new IllegalArgumentException(VALIDATE_ERROR); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
*/ | |
package com.bmchild.validation.constraints; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertFalse; | |
import static org.junit.Assert.assertTrue; | |
import static org.mockito.Mockito.when; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import javax.validation.ConstraintValidatorContext; | |
import org.junit.Test; | |
import org.junit.runner.RunWith; | |
import org.mockito.Mockito; | |
import org.mockito.runners.MockitoJUnitRunner; | |
import org.springframework.test.util.ReflectionTestUtils; | |
import com.bmchild.test.validation.AbstractConstraintValidatorTest; | |
/** | |
* @author bchild | |
* | |
*/ | |
@RunWith(MockitoJUnitRunner.class) | |
public class AllFieldsOrNoneConstraintValidatorTest extends AbstractConstraintValidatorTest { | |
private AllFieldsOrNoneConstraintValidator validator = new AllFieldsOrNoneConstraintValidator(); | |
private ConstraintValidatorContext context = super.getMockedConstraintValidatorContext(); | |
private AllFieldsOrNoneConstraintValidatorTestTestObject testObject = new AllFieldsOrNoneConstraintValidatorTestTestObject(); | |
@org.junit.Before | |
public void init() { | |
ReflectionTestUtils.setField(validator, "errorMessage", "ERROR"); | |
ReflectionTestUtils.setField(validator, "fields", Arrays.asList("fieldOne", "fieldTwo")); | |
testObject.setFieldOne("BOOYA"); | |
testObject.setFieldTwo("BOOYA AGAIN"); | |
} | |
/** | |
* Test method for {@link com.bmchild.validation.constraints.AllFieldsOrNoneConstraintValidator#initialize(com.bmchild.validation.constraints.AllFieldsOrNone)}. | |
*/ | |
@SuppressWarnings("unchecked") | |
@Test | |
public void testInitialize() { | |
String message = "I'm a message"; | |
String[] fields = new String[] {"one", "two"}; | |
AllFieldsOrNone constraintAnnotation = Mockito.mock(AllFieldsOrNone.class); | |
when(constraintAnnotation.message()).thenReturn(message); | |
when(constraintAnnotation.fields()).thenReturn(fields); | |
validator.initialize(constraintAnnotation); | |
String errorMessage = (String) ReflectionTestUtils.getField(validator, "errorMessage"); | |
assertEquals(message, errorMessage); | |
List<String> fieldNames = (List<String>) ReflectionTestUtils.getField(validator, "fields"); | |
assertEquals(fields.length, fieldNames.size()); | |
assertTrue(fieldNames.contains(fields[0])); | |
assertTrue(fieldNames.contains(fields[1])); | |
} | |
/** | |
* Test method for {@link com.bmchild.validation.constraints.AllFieldsOrNoneConstraintValidator#isValid(java.lang.Object, javax.validation.ConstraintValidatorContext)}. | |
*/ | |
@Test | |
public void testIsValid() { | |
// both populated | |
assertTrue(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidOneNullOnePopulated() { | |
testObject.setFieldTwo(null); | |
assertFalse(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidBothNull() { | |
testObject.setFieldTwo(null); | |
testObject.setFieldOne(null); | |
assertTrue(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidOneEmptyOneNull() { | |
testObject.setFieldTwo(""); | |
testObject.setFieldOne(null); | |
assertTrue(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidBothEmpty() { | |
testObject.setFieldTwo(""); | |
testObject.setFieldOne(""); | |
assertTrue(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidOneEmptyOnePopulated() { | |
testObject.setFieldOne(""); | |
assertFalse(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidListEmptyRestPopulated() { | |
ReflectionTestUtils.setField(validator, "fields", Arrays.asList("fieldOne", "fieldTwo", "fieldThree")); | |
testObject.setFieldThree(new ArrayList<String>()); | |
assertFalse(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidListNullRestPopulated() { | |
ReflectionTestUtils.setField(validator, "fields", Arrays.asList("fieldOne", "fieldTwo", "fieldThree")); | |
testObject.setFieldThree(null); | |
assertFalse(validator.isValid(testObject, context)); | |
} | |
@Test | |
public void testIsValidListPopulatedRestPopulated() { | |
ReflectionTestUtils.setField(validator, "fields", Arrays.asList("fieldOne", "fieldTwo", "fieldThree")); | |
testObject.setFieldThree(Arrays.asList("Tester")); | |
assertTrue(validator.isValid(testObject, context)); | |
} | |
public class AllFieldsOrNoneConstraintValidatorTestTestObject { | |
private String fieldOne; | |
private String fieldTwo; | |
private List<String> fieldThree; | |
public String getFieldOne() { | |
return fieldOne; | |
} | |
public void setFieldOne(String fieldOne) { | |
this.fieldOne = fieldOne; | |
} | |
public String getFieldTwo() { | |
return fieldTwo; | |
} | |
public void setFieldTwo(String fieldTwo) { | |
this.fieldTwo = fieldTwo; | |
} | |
public List<String> getFieldThree() { | |
return fieldThree; | |
} | |
public void setFieldThree(List<String> fieldThree) { | |
this.fieldThree = fieldThree; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
*/ | |
package com.bmchild.common.validation; | |
import javax.validation.ConstraintValidatorContext; | |
public abstract class ConstraintValidatorUtils { | |
public static void addConstraintViolation(ConstraintValidatorContext context, String message, String field) { | |
context.disableDefaultConstraintViolation(); | |
context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation(); | |
} | |
public static void addConstraintViolation(ConstraintValidatorContext context, String message) { | |
context.disableDefaultConstraintViolation(); | |
context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment