All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nz.co.senanque.validationengine.Binder Maven / Gradle / Ivy

Go to download

This is essentially a JAXB/XJC plugin that adds automatic validation to the generated Java classes. You define business objects in an XSD file, pass it through XJC and the plugin will add the validation code. It uses information in the XSD to pick up validation, and you can specify extensions to that in the XSD. The resulting Java classes check for validity when the setter is called and they reject attempts to set invalid values (this is a difference from other validation frameworks). The Java classes also expose a metadata interface to make it easy for UIs to generate, say, lists of options for a select box. The validation framework handles single field validation but you can inject a rule engine (or several) to handle cross field validation. But to any Java code the objects still look like ordinary Java beans. The surrounding application is unaware that they are anything different until they throw an exception. This makes it easy to use with frameworks that expect Java beans, and most of them do.

There is a newer version: 3.3.5
Show newest version
/*******************************************************************************
 * Copyright (c)2014 Prometheus Consulting
 *
 * 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 nz.co.senanque.validationengine;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import nz.co.senanque.validationengine.annotations.Ignore;
import nz.co.senanque.validationengine.metadata.ClassMetadata;
import nz.co.senanque.validationengine.metadata.PropertyMetadata;
import nz.co.senanque.validationengine.metadata.PropertyMetadataImpl;

import org.springframework.context.support.MessageSourceAccessor;

/**
 * 
 * This class is responsible for binding the graphed objects into the validator
 * session The arrays being used must be converted to listener arrays. 
 * 
 * @author Roger Parkinson
 * @version $Revision: 1.11 $
 */
public class Binder
{
    private final transient ValidationEngineImpl m_validationEngine;

    public Binder(final ValidationEngineImpl validationEngine)
    {
        m_validationEngine = validationEngine;
    }

    public Map bind(final ValidationObject object,
            final ValidationSession session, ValidationObject owner)
    {
        return bind(object, session, null, null,owner);
    }

    public Map bind(final ValidationObject object,
            final ValidationSession session, final ProxyField parent, final Integer index, ValidationObject owner)
    {
        MessageSourceAccessor messageSourceAccessor = new MessageSourceAccessor(m_validationEngine.getMessageSource());
        final Map ret = new IdentityHashMap();
        if (object == null)
        {
            return ret;
        }
        Class clazz = object.getClass();
        final ClassMetadata classMetadata = m_validationEngine
                .getClassMetadata(clazz);
        if (classMetadata == null)
        {
        	throw m_validationEngine.getLocaleAwareExceptionFactory().getRuntimeException("nz.co.senanque.validationengine.class.not.recognised", new Object[]{clazz.getName()});
        }
        object.setValidationSession(session);
        final ProxyObject proxyObject = new ProxyObject(object, parent, index,session);
        ret.put(object, proxyObject);
        final ObjectMetadata objectMetadata = (ObjectMetadata)object.getMetadata();
        objectMetadata.setClassMetadata(proxyObject, classMetadata);
        Map propertyMap = ValidationUtils.getProperties(object.getClass());
        for (Property property: propertyMap.values()) {
        	String fieldName = property.getFieldName();
            final PropertyMetadataImpl propertyMetadata = (PropertyMetadataImpl)classMetadata
                    .getField(fieldName);
            if (propertyMetadata == null)
            {
                continue;
            }
            final ProxyFieldImpl proxyField = new ProxyFieldImpl(fieldName,
                    proxyObject, parent, propertyMetadata, messageSourceAccessor);
            proxyObject.put(fieldName, proxyField);
            Method method = property.getGetter();
            if (property.getGetter().getReturnType().isAssignableFrom(List.class))
            {
                try
                {
                    @SuppressWarnings("unchecked")
					final List validationObjects = (List) method.invoke(object,new Object[]{});
                    final ListeningArray array = new ListeningArray();
                    array.addAll(validationObjects);
                    array.setValidationSession(session);
                    array.setProxyField(proxyField);
                    final Method setterMethod = property.getSetter();
                    setterMethod.invoke(object, array);
                    int index1 = 0;
                    for (ValidationObject child : validationObjects)
                    {
                        ret.putAll(bind(child, session, proxyField,index1++,owner));
                    }
                }
                catch (Exception e)
                {
                    throw new RuntimeException(e);
                }
                continue;
            }
            if (m_validationEngine.getClassMetadata(method.getReturnType()) != null)
            {
                try
                {
                    final ValidationObject child = (ValidationObject)method.invoke(object, new Object[]{});
                    ret.putAll(bind(child, session, proxyField,null,owner));
                }
                catch (Exception e)
                {
                    throw new RuntimeException(e);
                }
                continue;
            }
            try
            {
                Method getter = ValidationUtils.figureGetter(fieldName,clazz);
                if (getter.isAnnotationPresent(Ignore.class)) {
                	continue;
                }
                java.lang.reflect.Field propertyField = getField(clazz,fieldName);
            	Object value = ConvertUtils.convertToObject(propertyField.getType());
                proxyField.setInitialValue(value);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
        return ret;
    }
    private Field getField(Class clazz, String fieldName) throws Exception
    {
        try
        {
            java.lang.reflect.Field propertyField = clazz.getDeclaredField(fieldName);
            return propertyField;
        }
        catch (Exception e)
        {
            Class superClass = clazz.getSuperclass();
            if (superClass != null)
            {
                return getField(superClass,fieldName);
            }
            else
            {
                throw e;
            }
        }
    }
    public void unbindAll(final Map boundMap)
    {
    	for (ValidationObject validationObject: boundMap.keySet()) {
    		validationObject.setValidationSession(null);
    	}
        boundMap.clear();
    }

	/**
	 * Unbind the given object. We have to locate any attached objects and unbind them first.
	 * @param session
	 * @param validationObject
	 * @param boundMap
	 */
	public void unbind(ValidationSession session, ValidationObject validationObject,
			Map boundMap) {
		if (validationObject == null) {
			return;
		}
		// For all the fields: look for attached objects.
		ProxyObject proxyObject = session.getProxyObject(validationObject);
		Map fieldMap = proxyObject.getFieldMap();
		for (String fieldName: fieldMap.keySet()) {
			if (fieldName != null) {
				final ClassMetadata classMetadata = m_validationEngine.getClassMetadata(validationObject.getClass());
				final PropertyMetadata fieldMetadata = classMetadata.getField(fieldName);
				if (fieldMetadata == null) {
					continue;
				}
				ProxyField proxyField = proxyObject.getProxyField(fieldName);
				Method getter = proxyField.getGetter();
				if (getter.getReturnType().isAssignableFrom(List.class)) {
					// if this is a list then walk the list and unbind the objects there.
					try {
						@SuppressWarnings("unchecked")
						final List validationObjects = 
								(List) getter.invoke(validationObject, new Object[] {});
						for (ValidationObject child : validationObjects) {
							m_validationEngine.unbind(session, proxyField, child, boundMap);
							if (child != null) {
								child.setValidationSession(null);
							}
						}
					} catch (Exception e) {
						throw new RuntimeException(e);
					}
					continue;
				}
				if (m_validationEngine.getClassMetadata(getter.getReturnType()) != null) {
					// if this is a known object then unbind it
					try {
						ValidationObject child = (ValidationObject) getter
								.invoke(validationObject, new Object[] {});
						m_validationEngine.unbind(session, proxyField, child, boundMap);
						if (child != null) {
							child.setValidationSession(null);
						}
					} catch (Exception e) {
						throw new RuntimeException(e);
					}
					continue;
				}
			}
		}
	}
}