nz.co.senanque.validationengine.ValidationEngineImpl.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.senanque.validationengine.ValidationEngineImpl.java

Source

/*******************************************************************************
 * 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.annotation.Annotation;
import java.util.ArrayList;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.PostConstruct;

import nz.co.senanque.resourceloader.MessageResource;
import nz.co.senanque.validationengine.choicelists.Choice;
import nz.co.senanque.validationengine.choicelists.ChoiceBase;
import nz.co.senanque.validationengine.fieldvalidators.FieldValidator;
import nz.co.senanque.validationengine.metadata.ClassMetadata;
import nz.co.senanque.validationengine.metadata.EngineMetadata;
import nz.co.senanque.validationengine.metadata.PropertyMetadata;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.stereotype.Service;

/** 
 * This is the validation engine implementation. 
 * It implements validation on individual fields, including custom validation, as well as labels etc.
 * It accepts plugins for cross field validation and generation of new data.
 * 
 * @author Roger Parkinson
 * @version $Revision: 1.6 $
 */
@Service("validationEngine")
@MessageResource("ValidationMessages")
//@Vetoed
public final class ValidationEngineImpl implements ValidationEngine, MessageSourceAware {
    private final static Logger log = LoggerFactory.getLogger(ValidationEngineImpl.class);

    @Autowired
    private transient EngineMetadata m_metadata;
    @Autowired
    private transient LocaleAwareExceptionFactory m_localeAwareExceptionFactory;
    private transient MessageSource m_messageSource;
    private transient Binder m_binder = new Binder(this);
    @Autowired(required = false)
    private transient List<Plugin> m_plugins;
    @Value("${nz.co.senanque.validationengine.ValidationEngineImpl.identifier:not-set}")
    private transient String m_identifier;

    public Map<ValidationObject, ProxyObject> add(final ListeningArray<ValidationObject> array,
            final ValidationObject value, final ValidationSession session) throws ValidationException {
        log.debug("add {} {}", array.getClass(), value);
        final ProxyField proxyField = array.getProxyField();
        proxyField.useCurrentValue(false);
        final Map<ValidationObject, ProxyObject> bound = getBinder().bind(value, session, proxyField,
                new Integer(array.size()), null);
        session.provisionalObjects(bound);
        try {
            for (Plugin plugin : getPlugins()) {
                ProxyObject ownerProxyObject = proxyField.getProxyObject();
                plugin.bind(session, bound, proxyField, (ValidationObject) ownerProxyObject.getObject());
            }
        } finally {
            session.provisionalObjects(null);
        }
        return bound;
    }

    public Map<ValidationObject, ProxyObject> addAll(final ListeningArray<ValidationObject> listeningArray,
            final List<ValidationObject> o, final ValidationSession validationSession) {
        final Map<ValidationObject, ProxyObject> bound = new IdentityHashMap<ValidationObject, ProxyObject>();
        for (ValidationObject object : o) {
            bound.putAll(add(listeningArray, object, validationSession));
        }
        return bound;
    }

    public void removedFrom(final ListeningArray<?> array, final ValidationObject value,
            final ValidationSession session) {
        log.debug("remove {} {}", array.getClass(), value);
        final ProxyField proxyField = array.getProxyField();
        session.unbind(proxyField, value);
        return;
    }

    public String getStats(final ValidationSession session) {
        StringBuilder ret = new StringBuilder();
        for (Plugin plugin : getPlugins()) {
            ret.append(plugin.getStats(session));
        }
        return ret.toString();
    }

    public void set(final ValidationObject object, final String name, final Object newValue,
            final Object currentValue, final ValidationSession session) throws ValidationException {
        log.debug("set {} {}", name, newValue);
        if (newValue != null && newValue.equals(currentValue)) {
            return;
        }
        PropertyMetadata fieldMetadata = getMetadata().getField(object, name);
        if (fieldMetadata == null) {
            return;
        }

        if (newValue == null && currentValue == null) {
            return;
        }
        ObjectMetadata objectMetadata = session.getMetadata(object);
        ProxyField proxyField = objectMetadata.getProxyField(name);
        proxyField.useCurrentValue(false);
        final ClassMetadata clazz = getMetadata()
                .getClassMetadata(newValue != null ? newValue.getClass() : currentValue.getClass());
        if (clazz != null) {
            // We are setting a known object, as opposed to a primitive or a String etc
            Map<ValidationObject, ProxyObject> bound = null;
            if (currentValue != null) {
                session.unbind(proxyField, (ValidationObject) currentValue);
            }
            if (newValue != null) {
                validate(fieldMetadata, newValue, proxyField);
                proxyField.setValue(newValue);
            } else {
                proxyField.reset();
            }
            bound = session.bind((ValidationObject) newValue, object);
            session.provisionalObjects(bound);
            try {
                for (Plugin plugin : getPlugins()) {
                    plugin.bind(session, bound, null, object);
                }
            } finally {
                session.provisionalObjects(null);
            }
        } else {
            Object oldValue = proxyField.getValue();
            List<History> oldHistory = proxyField.getHistory();
            if (proxyField.getChoiceList() != null && oldValue != null) {
                for (Plugin plugin : getPlugins()) {
                    plugin.set(session, proxyField, null);
                }
            }
            if (newValue != null) {
                try {
                    validate(fieldMetadata, newValue, proxyField);
                } catch (ValidationException e) {
                    for (Plugin plugin : getPlugins()) {
                        plugin.set(session, proxyField, oldValue);
                    }
                    throw e;
                }
                proxyField.setValue(newValue);
            } else {
                proxyField.reset();
            }
            try {
                for (Plugin plugin : getPlugins()) {
                    plugin.set(session, proxyField, newValue);
                }
            } catch (Exception e) {
                proxyField.setHistory(oldHistory);
                for (Plugin plugin : getPlugins()) {
                    plugin.set(session, proxyField, oldValue);
                }
                throw e;
            }
        }
    }

    public void invokeListeners(ValidationObject object, String name, Object newValue, Object currentValue,
            ValidationSession session) {
        PropertyMetadata fieldMetadata = getMetadata().getField(object, name);
        if (fieldMetadata == null) {
            return;
        }

        ObjectMetadata objectMetadata = session.getMetadata(object);
        ProxyField proxyField = objectMetadata.getProxyField(name);
        for (SetterListener listener : proxyField.getListeners()) {
            listener.run(object, name, newValue, session);
        }
    }

    public void addListener(ValidationObject object, String name, ValidationSession session,
            SetterListener listener) {
        PropertyMetadata fieldMetadata = getMetadata().getField(object, name);
        if (fieldMetadata == null) {
            return;
        }

        ObjectMetadata objectMetadata = session.getMetadata(object);
        ProxyField proxyField = objectMetadata.getProxyField(name);
        proxyField.addListener(listener);
    }

    public void validate(PropertyMetadata fieldMetadata, Object value, ProxyField proxyField) {
        for (FieldValidator<Annotation> fv : fieldMetadata.getConstraintValidators()) {
            fv.validate(value);
        }
        List<ChoiceBase> choiceList = proxyField.getChoiceList();
        if (choiceList != null) {
            boolean found = false;
            for (Choice choice : choiceList) {
                if (choice.getKey().equals(value.toString())) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                String labelName = fieldMetadata.getLabelName();
                MessageSourceAccessor messageSourceAccessor = new MessageSourceAccessor(m_messageSource);
                String message = messageSourceAccessor.getMessage("nz.co.senanque.validationengine.choicelist",
                        new Object[] { labelName, value }, "Failed choicelist validation.");
                throw new ValidationException(message);
            }
        }
    }

    public void setMessageSource(MessageSource arg0) {
        m_messageSource = arg0;
    }

    public ClassMetadata getClassMetadata(Class<?> clazz) {
        return getMetadata().getClassMetadata(clazz);
    }

    public Map<ValidationObject, ProxyObject> bind(ValidationObject object, ValidationSession session,
            ValidationObject owner) {
        Map<ValidationObject, ProxyObject> ret = getBinder().bind(object, session, owner);
        session.provisionalObjects(ret);
        try {
            for (Plugin plugin : getPlugins()) {
                plugin.bind(session, ret, null, owner);
            }
        } finally {
            session.provisionalObjects(null);
        }
        return ret;
    }

    public void unbind(ValidationSession session, ProxyField proxyField, ValidationObject validationObject,
            Map<ValidationObject, ProxyObject> boundMap) {
        if (validationObject == null) {
            return;
        }
        // this actually unbinds all child objects, not the current object
        getBinder().unbind(session, validationObject, boundMap);
        // once all the child objects are unbound then tell the plugins
        // to unbind this one
        ProxyObject proxyObject = boundMap.get(validationObject);
        if (proxyObject == null) {
            return;
        }
        for (Plugin plugin : getPlugins()) {
            plugin.unbind(session, proxyField, validationObject);
        }
        ProxyObject removed = boundMap.remove(validationObject);
        if (removed == null) {
            log.warn("failed to remove {} from boundMap", validationObject.toString());
        }
    }

    public void unbindAll(ValidationSession session, Map<ValidationObject, ProxyObject> boundMap) {
        for (ValidationObject validationObject : boundMap.keySet()) {
            for (Plugin plugin : getPlugins()) {
                plugin.unbind(session, null, validationObject);
            }
        }
        getBinder().unbindAll(boundMap);
    }

    private Binder getBinder() {
        return m_binder;
    }

    public EngineMetadata getMetadata() {
        return m_metadata;
    }

    public void setMetadata(EngineMetadata metadata) {
        m_metadata = metadata;
    }

    public List<Plugin> getPlugins() {
        return m_plugins;
    }

    @SuppressWarnings("unchecked")
    public <T> T getPlugin(Class<T> clazz) {
        for (Plugin plugin : m_plugins) {
            if (plugin.getClass().equals(clazz)) {
                return (T) plugin;
            }
        }
        return null;
    }

    public void setPlugins(List<Plugin> plugins) {
        m_plugins = plugins;
    }

    public MessageSource getMessageSource() {
        return m_messageSource;
    }

    public boolean clean(ValidationObject object) {
        return false;
    }

    public ValidationSession createSession() {
        return new ValidationSession(this, Locale.getDefault());
    }

    public ValidationSession createSession(Locale locale) {
        return new ValidationSession(this, locale);
    }

    public void close(ValidationSession validationSession) {
        for (Plugin plugin : getPlugins()) {
            plugin.close(validationSession);
        }
        validationSession.unbindAll();
    }

    @PostConstruct
    public void init() {
        if (getPlugins() == null) {
            setPlugins(new ArrayList<Plugin>());
        }
        for (Plugin plugin : getPlugins()) {
            plugin.init(getMetadata());
        }
    }

    public String getIdentifier() {
        return m_identifier;
    }

    public void setIdentifier(String identifier) {
        m_identifier = identifier;
    }

    /* (non-Javadoc)
     * @see nz.co.senanque.validationengine.ValidationEngine#getCurrentTime()
     */
    @Override
    public long getCurrentTime() {
        // just return the current ms
        return new Date().getTime();
    }

    public LocaleAwareExceptionFactory getLocaleAwareExceptionFactory() {
        return m_localeAwareExceptionFactory;
    }

    public void setLocaleAwareExceptionFactory(LocaleAwareExceptionFactory localeAwareExceptionFactory) {
        m_localeAwareExceptionFactory = localeAwareExceptionFactory;
    }
}