Skip to content

Instantly share code, notes, and snippets.

@bmchild
Created September 26, 2012 19:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmchild/3790115 to your computer and use it in GitHub Desktop.
Save bmchild/3790115 to your computer and use it in GitHub Desktop.
AllFieldsOrNone SpringMVC 3 JSR-303 Validator
// package & imports
@AllFieldsOrNone(fields = {"stringOne", "stringTwo"} )
public class MyBean {
private String stringOne;
private String stringTwo;
//...getters & setters
}
/**
*
*/
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();
}
/**
*
*/
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);
}
}
}
}
/**
*
*/
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;
}
}
}
/**
*
*/
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