SpringOvalValidator.java

/**
 * Copyright 2014 Global Crop Diversity Trust
 *
 * 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 org.genesys.spring.validation.oval.spring;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import net.sf.oval.exception.ValidationFailedException;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;

public class SpringOvalValidator implements org.springframework.validation.Validator, InitializingBean {

	private Validator validator;

	public SpringOvalValidator() {
		validator = new Validator();
	}

	public SpringOvalValidator(Validator validator) {
		this.validator = validator;
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}

	@Override
	public void validate(Object target, Errors errors) {
		doValidate(target, errors, "");
	}

	private void doValidate(Object target, Errors errors, String fieldPrefix) {
		try {
			for (final ConstraintViolation violation : validator.validate(target)) {
				final OValContext ctx = violation.getContext();
				final String errorCode = violation.getErrorCode();
				final String errorMessage = violation.getMessage();

				if (ctx instanceof FieldContext) {
					final String fieldName = fieldPrefix + ((FieldContext) ctx).getField().getName();
					errors.rejectValue(fieldName, errorCode, errorMessage);
				} else {
					errors.reject(errorCode, errorMessage);
				}
			}

			try {
				final Field[] fields = getFields(target);
				for (final Field field : fields) {
					final SpringValidateNestedProperty validate = field.getAnnotation(SpringValidateNestedProperty.class);
					if (validate != null) {
						if (!field.isAccessible()) {
							field.setAccessible(true);
						}
						final Object nestedProperty = field.get(target);
						if (nestedProperty != null) {
							final String name = field.getName();
							if (nestedProperty instanceof Collection<?>) {
								// valueToValidate is a collection
								final Collection<?> col = (Collection<?>) nestedProperty;

								int index = 0;
								for (final Object object : col) {
									doValidate(object, errors, name + "[" + index + "].");
									index++;
								}
							} else if (nestedProperty instanceof Map<?, ?>) {
								// valueToValidate is a map, but only as a
								// string key
								final Map<?, ?> map = (Map<?, ?>) nestedProperty;

								for (final Map.Entry<?, ?> entry : map.entrySet()) {
									final Object key = entry.getKey();
									if (!(key instanceof String)) {
										throw new IllegalArgumentException("Map as a nested property supports only String keys for validation");
									}

									doValidate(entry.getValue(), errors, name + "['" + key + "']");
								}
							} else if (nestedProperty.getClass().isArray()) {
								// valueToValidate is an array
								final int length = Array.getLength(nestedProperty);
								for (int i = 0; i < length; i++) {
									final Object o = Array.get(nestedProperty, i);
									doValidate(o, errors, name + "[" + i + "].");
								}
							} else {
								// valueToValidate is other object
								doValidate(nestedProperty, errors, name + ".");
							}
						}
					}
				}
			} catch (final Exception e) {
				throw new RuntimeException(e);
			}

		} catch (final ValidationFailedException ex) {
			errors.reject(ex.getMessage());
		}
	}

	private Field[] getFields(Object target) {
		final Class<?> clazz = target.getClass();
		final List<Field> fields = doGetFields(clazz);
		return fields.toArray(new Field[fields.size()]);
	}

	private List<Field> doGetFields(Class<?> clazz) {
		final ArrayList<Field> list = new ArrayList<Field>();
		final Field[] fields = clazz.getDeclaredFields();
		list.addAll(Arrays.asList(fields));
		if (clazz.getSuperclass() != null) {
			list.addAll(doGetFields(clazz.getSuperclass()));
		}
		return list;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(validator, "Property [validator] is not set");
	}

	public Validator getValidator() {
		return validator;
	}

	public void setValidator(Validator validator) {
		this.validator = validator;
	}
}