Skip to content

Instantly share code, notes, and snippets.

@sscovil
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sscovil/9815169 to your computer and use it in GitHub Desktop.
Save sscovil/9815169 to your computer and use it in GitHub Desktop.
Java Object Validation via Annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Use this annotation to mark properties of a class as required in one or more validation contexts (e.g. CREATE,
* UPDATE), then validate instances of that class using the static Validator.validate(object, context) method.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Required {
/**
* ValidationContext(s) in which a property is required.
*/
ValidationContext[] context();
/**
* Optional custom validation error message.
*/
String message() default "This field is required.";
/**
* Set to true if this property is an object that should be validated recursively.
*/
boolean introspect() default false;
/**
* Set to true if this property is not required, but should be validated recursively when set. Use with introspect.
*/
boolean nullable() default false;
}
public enum ValidationContext {
CREATE,
UPDATE
}
import java.util.Map;
public class ValidationException extends RuntimeException {
private Map<String, String> errorCollection;
public ValidationException(String message) {
super(message);
}
public ValidationException(String message, Exception e) {
super(message, e);
}
public ValidationException(Exception e) {
super(e);
}
public ValidationException(Map<String, String> errorCollection) {
super();
this.errorCollection = errorCollection;
}
public Map<String, String> getErrorCollection() {
return errorCollection;
}
}
import java.util.Map;
public interface ValidationFilter {
public void execute(Map<String, String> errorMap, ValidationContext context);
}
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Validator {
/**
* Validate
*
* @param validationObject Object to validate.
* @param context ValidationContext to compare with @Required annotations for validationObject fields.
* @throws ValidationException
*/
public static void validate(Object validationObject, ValidationContext context) throws ValidationException {
validate(validationObject, context, null);
}
/**
* Validate w/Filter
*
* @param validationObject Object to validate.
* @param context ValidationContext to compare with @Required annotations for validationObject fields.
* @param filter Optional ValidationFilter used to filter the errorMap before an exception is thrown.
* @throws ValidationException
*/
public static void validate(
Object validationObject,
ValidationContext context,
ValidationFilter filter
) throws ValidationException {
Map<String, String> errorMap = executeValidation(validationObject, context, null);
if (filter != null)
filter.execute(errorMap, context);
if(!errorMap.isEmpty())
throw new ValidationException(errorMap);
}
/**
* Execute Validation
*
* @param validationObject Object to validate.
* @param context ValidationContext to compare with @Required annotations for validationObject fields.
* @return ErrorMap containing missing required field names as keys and their @Required messages as values.
*/
private static Map<String, String> executeValidation(
Object validationObject,
ValidationContext context,
String parentName
) {
Map<String, String> errorMap = new HashMap<String, String>();
Field[] fields = validationObject.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Required.class)) {
Required required = field.getAnnotation(Required.class);
if (requiredInContext(required.context(), context)) {
Object value = getFieldValue(validationObject, field.getName());
if ((value == null || value.toString().isEmpty()) && !required.nullable()) {
if (parentName == null) parentName = "";
else if (!parentName.isEmpty()) parentName += ".";
errorMap.put(parentName + field.getName(), required.message());
}
else if (value != null && required.introspect()) {
// Use recursion to perform introspective validation on property.
errorMap.putAll(executeValidation(value, context, field.getName()));
}
}
}
}
return errorMap;
}
/**
* Required In Context
*
* @param requiredContexts Array of required ValidationContexts.
* @param currentContext ValidationContext to search for in requiredContexts.
* @return True if currentContext is present in requiredContexts; otherwise false.
*/
private static boolean requiredInContext(ValidationContext[] requiredContexts, ValidationContext currentContext) {
if (requiredContexts != null && requiredContexts.length > 0)
for (ValidationContext requiredContext : requiredContexts)
if (requiredContext.equals(currentContext))
return true;
return false;
}
/**
* Get Field Value
*
* @param object Object to get the field value from.
* @param fieldName Name of field to get the value of.
* @return Value of the specified object field.
*/
private static Object getFieldValue(Object object, String fieldName) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
catch (NullPointerException e) {
return null;
}
catch (NoSuchFieldException e) {
return null;
}
catch (IllegalAccessException e) {
return null;
}
}
}
import com.mediasilo.api.validation.ValidationContext;
import com.mediasilo.api.validation.ValidationException;
import com.mediasilo.api.validation.Validator;
import org.testng.annotations.Test;
public class ValidatorTest {
@Test
public void createValidatorPasses() {
ValidatorTestObject object = new ValidatorTestObject(null, "required", "required");
Validator.validate(object, ValidationContext.CREATE);
}
@Test(expectedExceptions = ValidationException.class)
public void createValidatorFails() {
ValidatorTestObject object = new ValidatorTestObject();
Validator.validate(object, ValidationContext.CREATE);
}
@Test
public void updateValidatorPasses() {
ValidatorTestObject object = new ValidatorTestObject(12345, null, "required");
Validator.validate(object, ValidationContext.UPDATE);
}
@Test(expectedExceptions = ValidationException.class)
public void updateValidatorFails() {
ValidatorTestObject object = new ValidatorTestObject();
Validator.validate(object, ValidationContext.UPDATE);
}
@Test
public void requireIntrospectPasses() {
ValidatorTestObjectProperty introspect = new ValidatorTestObjectProperty("required");
ValidatorTestObject object = new ValidatorTestObject(null, "required", "required", introspect);
Validator.validate(object, ValidationContext.CREATE);
}
@Test(expectedExceptions = ValidationException.class)
public void requireIntrospectFails() {
ValidatorTestObjectProperty introspect = new ValidatorTestObjectProperty();
ValidatorTestObject object = new ValidatorTestObject(null, "required", "required", introspect);
Validator.validate(object, ValidationContext.CREATE);
}
}
import com.mediasilo.api.validation.Required;
import com.mediasilo.api.validation.ValidationContext;
public class ValidatorTestObject {
@Required(context = ValidationContext.UPDATE, message = "This field is required on update.")
private Integer id;
@Required(context = ValidationContext.CREATE, message = "This field is required on create.")
private String foo;
@Required(context = {ValidationContext.CREATE, ValidationContext.UPDATE},
message = "This field is required on create and update.")
private String bar;
@Required(context = {ValidationContext.CREATE, ValidationContext.UPDATE}, introspect = true, nullable = true)
private ValidatorTestObjectProperty inner;
public ValidatorTestObject() {
}
public ValidatorTestObject(Integer id, String foo, String bar) {
this.id = id;
this.foo = foo;
this.bar = bar;
}
public ValidatorTestObject(Integer id, String foo, String bar, ValidatorTestObjectProperty inner) {
this.id = id;
this.foo = foo;
this.bar = bar;
this.inner = inner;
}
}
import com.mediasilo.api.validation.Required;
import com.mediasilo.api.validation.ValidationContext;
public class ValidatorTestObjectProperty {
@Required(context = {ValidationContext.CREATE, ValidationContext.UPDATE})
private String required;
private String notRequired;
public ValidatorTestObjectProperty() {
}
public ValidatorTestObjectProperty(String required) {
this.required = required;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment