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

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

The newest version!
/*
 * #%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%
 */
package org.nuiton.validator.bean;

import com.google.common.base.Preconditions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.validator.NuitonValidator;
import org.nuiton.validator.NuitonValidatorModel;
import org.nuiton.validator.NuitonValidatorProvider;
import org.nuiton.validator.NuitonValidatorProviders;
import org.nuiton.validator.NuitonValidatorResult;
import org.nuiton.validator.NuitonValidatorScope;

import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Objects;

/**
 * Validator for a javaBean object.
 * 

* A such validator is designed to validate to keep the validation of a bean, * means the bean is attached to the validator (via the context field {@link #context}. *

* A such validator is also a JavaBean and you can listen his states * modifications via the classic java bean api. * * Note: The {@link org.nuiton.validator.bean.BeanValidator} should never be used for * validation in a service approach since it needs to keep a reference to the * bean to validate. * * @author Tony Chemit - [email protected] * @see BeanValidatorListener * @since 2.5.2 */ public class BeanValidator extends AbstractValidator { /** * Name of the bounded property {@code bean}. * * @see #getBean() * @see #setBean(Object) */ public static final String BEAN_PROPERTY = "bean"; private static final Logger log = LogManager.getLogger(BeanValidator.class); /** * Context of the registered bean to validate. * * @since 2.5.2 */ protected final BeanValidatorContext context; /** * To chain to another validator (acting as parent of this one). * * @since 2.5.2 */ protected BeanValidator parentValidator; /** * Obtain a new {@link BeanValidator} for the given parameters. * * Note: It will use the default provider of {@link NuitonValidator} * * @param type type of bean to validate * @param context context of validation * @param scopes authorized scopes (if {@code null}, will use all scopes) * @param type of bean to validate * @return the new instantiated {@link BeanValidator}. * @see NuitonValidatorProviders#getDefaultProviderName() */ public static BeanValidator newValidator(Class type, String context, NuitonValidatorScope... scopes) { // get the provider default name String providerName = NuitonValidatorProviders.getDefaultProviderName(); // get the bean validator with this provider return newValidator(providerName, type, context, scopes); } /** * Obtain a new {@link BeanValidator} for the given parameters. * * Note: It will use the provider of {@link NuitonValidator} * defined by the {@code providerName}. * * @param providerName name of {@link NuitonValidator} to use * @param type type of bean to validate * @param context context of validation * @param scopes authorized scopes (if {@code null}, will use all scopes) * @param type of bean to validate * @return the new instantiated {@link BeanValidator}. * @throws NullPointerException if type is {@code null} * @see NuitonValidatorProviders#getProvider(String) */ public static BeanValidator newValidator(String providerName, Class type, String context, NuitonValidatorScope... scopes) { Objects.requireNonNull(type, "type parameter can not be null."); // get delegate validator provider NuitonValidatorProvider provider = NuitonValidatorProviders.getProvider(providerName); // create the new instance of bean validator return new BeanValidator<>(provider, type, context, scopes); } public BeanValidator(NuitonValidatorProvider validatorProvider, Class beanClass, String context) { this(validatorProvider, beanClass, context, NuitonValidatorScope.values()); } public BeanValidator(NuitonValidatorProvider validatorProvider, Class beanClass, String context, NuitonValidatorScope... scopes) { super(validatorProvider, beanClass); this.context = new BeanValidatorContext<>(); // build delegate validator rebuildDelegateValidator(beanClass, context, scopes); // context has changed firePropertyChange(CONTEXT_PROPERTY, null, context); // scopes has changed firePropertyChange(SCOPES_PROPERTY, null, scopes); } /** * Obtain the actual bean attached to the validator. * * @return the bean attached to the validor or {@code null} if no bean * is attached */ public O getBean() { return context.getBean(); } /** * Change the attached bean. *

* As a side effect, the internal * {@link BeanValidatorContext#getMessages()} will be reset. * * @param bean the bean to attach (can be {@code null} to reset the * validator). */ public void setBean(O bean) { O oldBean = getBean(); if (log.isDebugEnabled()) { log.debug(this + " : " + bean); } if (oldBean != null) { try { BeanUtil.removePropertyChangeListener(l, oldBean); } catch (Exception eee) { if (log.isInfoEnabled()) { log.info("Can't unregister as listener for bean " + oldBean.getClass() + " for reason " + eee.getMessage(), eee); } } } context.setBean(bean); if (bean == null) { // remove all messages for all fields of the validator mergeMessages(null); } else { try { BeanUtil.addPropertyChangeListener(l, bean); } catch (Exception eee) { if (log.isInfoEnabled()) { log.info("Can't register as listener for bean " + bean.getClass() + " for reason " + eee.getMessage(), eee); } } validate(); } setChanged(false); setValid(context.isValid()); firePropertyChange(BEAN_PROPERTY, oldBean, bean); } public BeanValidator getParentValidator() { return parentValidator; } public void setParentValidator(BeanValidator parentValidator) { this.parentValidator = parentValidator; } @Override public boolean hasFatalErrors() { return context.hasFatalErrors(); } @Override public boolean hasErrors() { return context.hasErrors(); } @Override public boolean hasWarnings() { return context.hasWarnings(); } @Override public boolean hasInfos() { return context.hasInfos(); } @Override public boolean isValid(String fieldName) { // field is valid if no fatal messages nor error messages return context.isValid(fieldName); } @Override public NuitonValidatorScope getHighestScope(String field) { return context.getHighestScope(field); } @Override public void doValidate() { validate(); setValid(context.isValid()); setChanged(true); } public void addBeanValidatorListener(BeanValidatorListener listener) { listenerList.add(BeanValidatorListener.class, listener); } public void removeBeanValidatorListener(BeanValidatorListener listener) { listenerList.remove(BeanValidatorListener.class, listener); } public BeanValidatorListener[] getBeanValidatorListeners() { return listenerList.getListeners(BeanValidatorListener.class); } @Override protected void doValidate(O bean) { Preconditions.checkState( Objects.equals(bean, getBean()), "Can not validate the bean [" + bean + "] which is not the one registered [" + bean + "] in this validator."); doValidate(); } @Override protected NuitonValidator getDelegate() { return context.getValidator(); } @Override protected void rebuildDelegateValidator(Class beanType, String context, NuitonValidatorScope... scopes) { // changing context could change fields definition // so dettach bean, must rebuild the fields // Dettach the bean before any thing, because with the new delegate // validator some old fields could not be used any longer, and then // listeners will never have the full reset of their model... if (getBean() != null) { setBean(null); } if (scopes == null || scopes.length == 0) { scopes = NuitonValidatorScope.values(); } // compute the new validator model NuitonValidatorModel validatorModel = validatorProvider.getModel(beanType, context, scopes ); // remove old delegate validator NuitonValidator delegate = validatorProvider.newValidator(validatorModel); this.context.setValidator(delegate); } /** * il faut eviter le code re-intrant (durant une validation, une autre est * demandee). Pour cela on fait la validation dans un thread, et tant que la * premiere validation n'est pas fini, on ne repond pas aux solicitations. * Cette method est public pour permettre de force une validation par * programmation, ce qui est utile par exemple si le bean ne supporte pas * les {@link PropertyChangeListener} * * Note: la methode est protected et on utilise la methode * {@link #doValidate()} car la méthode ne modifie pas les etats * internes et cela en rend son utilisation delicate (le validateur entre * dans un etat incoherent par rapport aux messages envoyés). */ protected void validate() { // on ne valide que si il y a un bean et que le resultat de la validation // pourra etre affiche quelque part if (isCanValidate()) { NuitonValidatorResult result = context.validate(); mergeMessages(result); if (parentValidator != null) { // chained validation // the parent validator should not be changed from this validation boolean wasModified = parentValidator.isChanged(); parentValidator.doValidate(); if (!wasModified) { // push back old state parentValidator.setChanged(false); } } } } protected void fireFieldChanged(BeanValidatorEvent evt) { for (BeanValidatorListener listener : listenerList.getListeners(BeanValidatorListener.class)) { listener.onFieldChanged(evt); } } protected void mergeMessages(NuitonValidatorResult newMessages) { List> events = context.mergeMessages(this, newMessages); if (events != null) { // send all messages for (BeanValidatorEvent event : events) { fireFieldChanged(event); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy