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

org.nuiton.validator.bean.BeanValidatorContext Maven / Gradle / Ivy

The newest version!
package org.nuiton.validator.bean;

/*-
 * #%L
 * Validation :: API
 * %%
 * Copyright (C) 2021 - 2024 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.validator.NuitonValidator;
import org.nuiton.validator.NuitonValidatorResult;
import org.nuiton.validator.NuitonValidatorScope;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Created on 26/01/2024.
 *
 * @author Tony Chemit - [email protected]
 * @since 2.0.0
 */
public class BeanValidatorContext {
    private static final Logger log = LogManager.getLogger(BeanValidatorContext.class);

    /**
     * map of conversion errors detected by this validator
     */
    protected final Map conversionErrors;
    /**
     * Bean to validate.
     */
    protected O bean;
    /**
     * State of validation (keep all messages of validation for the filled
     * bean).
     */
    protected NuitonValidatorResult messages;
    /**
     * Validator.
     */
    protected NuitonValidator validator;
    /**
     * State to know if the validator can be used (we keep this state for
     * performance reasons : do not want to compute this value each time a
     * validation is asked...).
     */
    protected boolean canValidate;

    public BeanValidatorContext() {
        conversionErrors = new TreeMap<>();
    }


    public O getBean() {
        return bean;
    }

    public void setBean(O bean) {
        if (log.isDebugEnabled()) {
            log.debug(this + " : " + bean);
        }

        // clean conversions of previous bean
        conversionErrors.clear();
        this.bean = bean;

        setCanValidate(!validator.getEffectiveFields().isEmpty() && bean != null);
    }

    protected BeanValidatorEvent createEvent(BeanValidator source,
                                                O bean,
                                                String field,
                                                NuitonValidatorScope scope,
                                                String[] toAdd,
                                                String[] toDelete) {
        return new BeanValidatorEvent<>(
                source,
                field,
                scope,
                toAdd,
                toDelete
        );
    }

    public NuitonValidator getValidator() {
        return validator;
    }

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

    public NuitonValidatorResult getMessages() {
        return messages;
    }

    public boolean isCanValidate() {
        return canValidate;
    }

    public void setCanValidate(boolean canValidate) {
        this.canValidate = canValidate;
    }

    public boolean isValid() {
        return messages == null || messages.isValid();
    }

    public boolean hasFatalErrors() {
        return messages != null && messages.hasFatalMessages();
    }

    public boolean hasErrors() {
        return messages != null && messages.hasErrorMessagess();
    }

    public boolean hasWarnings() {
        return messages != null && messages.hasWarningMessages();
    }

    public boolean hasInfos() {
        return messages != null && messages.hasInfoMessages();
    }

    public boolean isValid(String fieldName) {

        // field is valid if no fatal messages nor error messages

        return !(
                messages.hasMessagesForScope(fieldName, NuitonValidatorScope.FATAL) ||
                        messages.hasMessagesForScope(fieldName, NuitonValidatorScope.ERROR));
    }

    public NuitonValidatorScope getHighestScope(String field) {

        return messages.getFieldHighestScope(field);
    }

    public NuitonValidatorResult validate() {
        NuitonValidatorResult result = validator.validate(bean, null);

        // treate conversion errors
        // reinject them
        for (Map.Entry entry : conversionErrors.entrySet()) {


            // remove from validation, errors occurs on this field
            String field = entry.getKey();


            List errors = result.getErrorMessages(field);

            String conversionError = entry.getValue();
            if (errors != null) {
                errors.clear();
                errors.add(conversionError);
            } else {
                errors = Collections.singletonList(conversionError);
            }

            result.setMessagesForScope(NuitonValidatorScope.ERROR, field, errors);
        }
        return result;
    }

    public List> mergeMessages(BeanValidator beanValidator,
                                                     NuitonValidatorResult newMessages) {

        if (newMessages == null && messages == null) {

            // no messages ever registered and ask to delete them, so nothing
            // to do
            return null;
        }

        Set scopes = getValidator().getEffectiveScopes();

        // list of events to send after the merge of messages
        List> events = new LinkedList<>();

        for (NuitonValidatorScope scope : scopes) {

            // do the merge at scope level
            mergeMessages(beanValidator, scope, newMessages, events);

        }

        if (newMessages != null) {

            //TODO tchemit 2011-01-23 Perhaps it will necessary to clear the messages for memory performance ?

            // finally keep the new messages as the current messages
            this.messages = newMessages;
        }

        return events;
    }

    protected void mergeMessages(BeanValidator beanValidator,
                                 NuitonValidatorScope scope,
                                 NuitonValidatorResult newMessages,
                                 List> events) {


        if (newMessages == null) {

            // special case to empty all messages

            List fieldsForScope = messages.getFieldsForScope(scope);

            for (String field : fieldsForScope) {
                List messagesForScope = messages.getMessagesForScope(field, scope);
                events.add(createEvent(beanValidator, bean, field, scope, null, messagesForScope.toArray(new String[0])));
            }

            // suppress all messages for this scope
            messages.clearMessagesForScope(scope);


        } else {

            List newFields = newMessages.getFieldsForScope(scope);

            if (messages == null) {

                // first time of a merge, just add new messages

                for (String field : newFields) {
                    List messagesForScope = newMessages.getMessagesForScope(field, scope);
                    events.add(createEvent(beanValidator, bean, field, scope, messagesForScope.toArray(new String[0]), null));
                }

                // nothing else to do
                return;
            }

            List oldFields = messages.getFieldsForScope(scope);

            Iterator itr;

            // detects field with only new messages
            itr = newFields.iterator();
            while (itr.hasNext()) {
                String newField = itr.next();

                if (!oldFields.contains(newField)) {

                    // this fields has now messages but not before : new messages
                    List messagesForScope = newMessages.getMessagesForScope(newField, scope);
                    events.add(createEvent(beanValidator, bean, newField, scope, messagesForScope.toArray(new String[0]), null));

                    // treated field
                    itr.remove();
                }
            }

            // detects fields with only obsolete messages
            itr = oldFields.iterator();
            while (itr.hasNext()) {
                String oldField = itr.next();

                if (!newFields.contains(oldField)) {

                    // this fields has no more messages
                    List messagesForScope = messages.getMessagesForScope(oldField, scope);
                    events.add(createEvent(beanValidator, bean, oldField, scope, null, messagesForScope.toArray(new String[0])));

                    // treated field
                    itr.remove();
                }
            }

            // now deal with mixte field (toAdd and toDelete)
            for (String field : newFields) {

                List newMessagesForScope = newMessages.getMessagesForScope(field, scope);
                List oldMessagesForScope = messages.getMessagesForScope(field, scope);

                // get old obsoletes messages to delete
                Set toDelete = new HashSet<>(oldMessagesForScope);
                newMessagesForScope.forEach(toDelete::remove);

                // get new messages to add
                Set toAdd = new HashSet<>(newMessagesForScope);
                oldMessagesForScope.forEach(toAdd::remove);

                events.add(createEvent(
                        beanValidator,
                        bean,
                        field,
                        scope,
                        toAdd.isEmpty() ? null : toAdd.toArray(new String[0]),
                        toDelete.isEmpty() ? null : toDelete.toArray(new String[0])
                ));

            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy