Программное создание валидатора значений примитивных типов

Мне необходимо проверять значения различных примитивных типов и их коллекций на соответствие динамически задаваемым ограничениям и правилам. Необходимо это для проверки/мониторинга возвращаемых значений парка CNC-машин, которые могут иметь различные диапазоны допустимых значений в зависимости от машины/заказа/времени/сотрудника/фазы луны и т.д.. Также проверки используются для определения превышения пороговых значений критических показателей с последующей генерацией событий.
К сожалению, вариант с сохранением значений в БД не допустим по непреодолимым обстоятельствам и поэтому, на данный момент, рассматриваются следующие варианты...

Вариант 1: свой "велосипед"

Писать свой "велосипед" и надеятся, что квалификации и опыта хватит на разработку хорошей библиотеки.


Вариант 2: имплементация Bean Validation API

Как первый вариант, с тем лишь различием, что конструироваться "велосипед" будет по определённым правилам, что поможет избежать квадратных колёс, но использование/надёжность/обслуживание всё равно будет под вопросом.


Вариант 3: Hibernate Validator

Использовать Bean Validation API (Hibernate Validator), где имеется возможность динамического создания правил. Пример использования ниже:

<!-- Maven dependencies -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.13.Final</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.valueextraction.ExtractedValue;
import javax.validation.valueextraction.ValueExtractor;

import org.hibernate.validator.cfg.defs.*;

public class App
{
    public static void main( final String[] args )
    {
        try ( ValidatorFactory factory = Validation
                .byProvider( HibernateValidator.class )
                .configure()
                .addMapping( buildPrimitiveConstraintMapping() )
                .addValueExtractor( new IntegerValueExtractor() )
                .addValueExtractor( new StringValueExtractor() )
                .messageInterpolator( new ParameterMessageInterpolator() )
                .buildValidatorFactory() )
        {
            Validator validator = factory.getValidator();

            validator.validate( 7 )
                .stream()
                .map( ConstraintViolation::getMessage )
                .forEach( System.out::println );
            
            validator.validate( "" )
                .stream()
                .map( ConstraintViolation::getMessage )
                .forEach( System.out::println );
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }
    }

    private static ConstraintMapping buildPrimitiveConstraintMapping()
    {
        ConstraintMapping mapping = new DefaultConstraintMapping();

        mapping
            .type( Integer.class )
            .constraint( new NotNullDef() )
            .constraint( new MinDef().value( 18 ) )
            .constraint( new MaxDef().value( 50 ) );

        mapping
            .type( String.class )
            .constraint( new NotNullDef() )
            .constraint( new NotBlankDef() );

        return mapping;
    }

    // Value extractor for Integer
    public static class IntegerValueExtractor
            implements
            ValueExtractor<@ExtractedValue(type = Integer.class) Integer>
    {
        @Override
        public void extractValues(
            Integer originalValue,
            ValueReceiver receiver )
        {
            receiver.value( null, originalValue );
        }
    }

    // Value extractor for String
    public static class StringValueExtractor
            implements
            ValueExtractor<@ExtractedValue(type = String.class) String>
    {
        @Override
        public void extractValues(
            String originalValue,
            ValueReceiver receiver )
        {
            receiver.value( null, originalValue );
        }
    }

}

Как принято решать такие проблемы "по фэн-шую"? Не является ли выбранная библиотека избыточной для решения поставленной задачи/проблемы?


Ответы (1 шт):

Автор решения: XelaNimed

Не знаю правильно-ли или нет, но решил так:

try (ValidatorFactory factory = Validation.byProvider( HibernateValidator.class )
                                .configure()
                                .addMapping( buildPrimitiveConstraintMapping() )
                                .addValueExtractor( new IntegerValueExtractor() )
                                .addValueExtractor( new StringValueExtractor() )
                                .messageInterpolator( new CustomMessageInterpolator() )
                                .buildValidatorFactory())
{
    Validator validator = factory.getValidator();

    validator
        .validate( 7 ).stream()
        .map( ConstraintViolation::getMessage )
        .forEach( System.out::println );
    validator
        .validate( "" ).stream()
        .map( ConstraintViolation::getMessage )
        .forEach( System.out::println );
}
catch( Exception e )
{
    e.printStackTrace();
}

private static ConstraintMapping buildPrimitiveConstraintMapping()
{
    ConstraintMapping mapping = new DefaultConstraintMapping();

    mapping
        .type( Integer.class )
        .constraint( new NotNullDef() )
        .constraint( new MinDef().value( 18 ) )
        .constraint( new MaxDef().value( 50 )
                .message( "The value {validatedValue} must not be greater than {value}" ) );

    mapping
        .type( String.class )
        .constraint( new NotNullDef() )
        .constraint( new NotBlankDef().message( "Value can't be null" ) );

    return mapping;
}

public static class IntegerValueExtractor implements ValueExtractor<@ExtractedValue(type = Integer.class) Integer>
{
    @Override public void extractValues( Integer originalValue, ValueReceiver receiver )
    {
        receiver.value( null, originalValue );
    }
}

public static class StringValueExtractor implements ValueExtractor<@ExtractedValue(type = String.class) String>
{
    @Override public void extractValues( String originalValue, ValueReceiver receiver )
    {
        receiver.value( null, originalValue );
    }
}

Класс CustomMessageInterpolator непосредственно не относится к поставленной задаче и можно использовать и далее ParameterMessageInterpolator, как показано в коде вопроса, но в этом случае будет невозможно использовать {validatedValue} в сообщениях об ошибках (скорее всего из-за того, что не используются многие зависимости HibernateValidator'а):

import java.util.Locale;
import java.lang.invoke.MethodHandles;

import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm;
import org.hibernate.validator.internal.engine.messageinterpolation.ParameterTermResolver;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator;

public class CustomMessageInterpolator extends AbstractMessageInterpolator
{

    private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

    @Override public String interpolate( Context context, Locale locale, String term )
    {
        if ( "{validatedValue}".equals( term ) )
        {
            return String.valueOf( context.getValidatedValue() );
        }
        else if ( InterpolationTerm.isElExpression( term ) )
        {
            LOG.warnElIsUnsupported( term );
            return term;
        }

        ParameterTermResolver parameterTermResolver = new ParameterTermResolver();
        return parameterTermResolver.interpolate( context, term );
    }

}
→ Ссылка