drame
drame

Reputation: 545

Hibernat Validator called twice

we develop an application using Jakarta EE 10 and Payara (Glassfish) 6.x. Using @Valid in my resource methods results in my custom validators beeing called twice. The first time everything is injected into the Validator class as expcected. I.e. @Context private UriInfo uriInfo; works like a charm. But the second time the validator is called no injection happens. With CDI managed beans we just manually look them up using CDI.current().select(Service.class).get();. But for UriInfo I couldn't find a similar approach.

Here are the stack trace from the first and second call to the validator. I removed the parts that are the same for both stack traces to improve readability.

isValid:82, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator (at.ppcssolution.domain.nursecall.api.validation)
isValid:41, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator (at.ppcssolution.domain.nursecall.api.validation)
validateSingleConstraint:180, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:66, SimpleConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:75, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForSingleDefaultGroupElement:518, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForDefaultGroup:488, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:450, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedAnnotatedObjectForCurrentGroup:629, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedConstraints:590, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParametersInContext:880, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:283, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:235, ValidatorImpl (org.hibernate.validator.internal.engine)
onValidate:154, DefaultConfiguredValidator (org.glassfish.jersey.server.validation.internal)
proceed:95, ValidationInterceptorExecutor (org.glassfish.jersey.server.validation.internal)
validateResourceAndInputParams:122, DefaultConfiguredValidator (org.glassfish.jersey.server.validation.internal)
invoke:136, AbstractJavaResourceMethodDispatcher (org.glassfish.jersey.server.model.internal)
doDispatch:159, JavaResourceMethodDispatcherProvider$VoidOutInvoker (org.glassfish.jersey.server.model.internal)
dispatch:93, AbstractJavaResourceMethodDispatcher (org.glassfish.jersey.server.model.internal)
invoke:478, ResourceMethodInvoker (org.glassfish.jersey.server.model)
apply:400, ResourceMethodInvoker (org.glassfish.jersey.server.model)
apply:81, ResourceMethodInvoker (org.glassfish.jersey.server.model)
run:274, ServerRuntime$1 (org.glassfish.jersey.server)
call:248, Errors$1 (org.glassfish.jersey.internal)
call:244, Errors$1 (org.glassfish.jersey.internal)
process:292, Errors (org.glassfish.jersey.internal)
process:274, Errors (org.glassfish.jersey.internal)
process:244, Errors (org.glassfish.jersey.internal)
runInScope:266, RequestScope (org.glassfish.jersey.process.internal)

The second failing call:

isValid:66, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator (at.ppcssolution.domain.nursecall.api.validation)
isValid$$super:-1, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator$Proxy$_$$_WeldSubclass (at.ppcssolution.domain.nursecall.api.validation)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
proceedInternal:51, TerminalAroundInvokeInvocationContext (org.jboss.weld.interceptor.proxy)
proceed:78, AroundInvokeInvocationContext (org.jboss.weld.interceptor.proxy)
intercept:53, WebAppExceptionInterceptor (org.glassfish.jersey.ext.cdi1x.transaction.internal)
invoke:-1, GeneratedMethodAccessor416 (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
invoke:73, SimpleInterceptorInvocation$SimpleMethodInvocation (org.jboss.weld.interceptor.reader)
proceedInternal:66, NonTerminalAroundInvokeInvocationContext (org.jboss.weld.interceptor.proxy)
proceed:78, AroundInvokeInvocationContext (org.jboss.weld.interceptor.proxy)
proceed:212, TransactionalInterceptorBase (org.glassfish.cdi.transaction)
transactional:115, TransactionalInterceptorRequired (org.glassfish.cdi.transaction)
invoke:-1, GeneratedMethodAccessor415 (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
invoke:73, SimpleInterceptorInvocation$SimpleMethodInvocation (org.jboss.weld.interceptor.reader)
executeAroundInvoke:84, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
executeInterception:72, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
invoke:56, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
invoke:79, CombinedInterceptorAndDecoratorStackMethodHandler (org.jboss.weld.bean.proxy)
invoke:68, CombinedInterceptorAndDecoratorStackMethodHandler (org.jboss.weld.bean.proxy)
isValid:-1, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator$Proxy$_$$_WeldSubclass (at.ppcssolution.domain.nursecall.api.validation)
isValid:39, ValidNurseCallComponentIdentifier$NurseCallComponentIdentifierValidator (at.ppcssolution.domain.nursecall.api.validation)
validateSingleConstraint:180, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:66, SimpleConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
validateConstraints:75, ConstraintTree (org.hibernate.validator.internal.engine.constraintvalidation)
doValidateConstraint:130, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateConstraint:123, MetaConstraint (org.hibernate.validator.internal.metadata.core)
validateMetaConstraint:555, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForSingleDefaultGroupElement:518, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForDefaultGroup:488, ValidatorImpl (org.hibernate.validator.internal.engine)
validateConstraintsForCurrentGroup:450, ValidatorImpl (org.hibernate.validator.internal.engine)
validateInContext:400, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedAnnotatedObjectForCurrentGroup:629, ValidatorImpl (org.hibernate.validator.internal.engine)
validateCascadedConstraints:590, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParametersInContext:880, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:283, ValidatorImpl (org.hibernate.validator.internal.engine)
validateParameters:235, ValidatorImpl (org.hibernate.validator.internal.engine)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
invoke:38, AbstractBeanInstance (org.jboss.weld.bean.proxy)
invoke:106, ProxyMethodHandler (org.jboss.weld.bean.proxy)
validateParameters:-1, Validator$ExecutableValidator$1931579803$Proxy$_$$_WeldClientProxy (org.jboss.weld.generated.proxies.validation)
validateMethodInvocation:66, ValidationInterceptor (org.hibernate.validator.cdi.internal.interceptor)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
invoke:73, SimpleInterceptorInvocation$SimpleMethodInvocation (org.jboss.weld.interceptor.reader)
executeAroundInvoke:84, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
executeInterception:72, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
invoke:56, InterceptorMethodHandler (org.jboss.weld.interceptor.proxy)
invoke:79, CombinedInterceptorAndDecoratorStackMethodHandler (org.jboss.weld.bean.proxy)
invoke:68, CombinedInterceptorAndDecoratorStackMethodHandler (org.jboss.weld.bean.proxy)
createWallButton:-1, WallButtonResource$Proxy$_$$_WeldSubclass (at.ppcssolution.domain.nursecall.rest)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
lambda$static$0:52, ResourceMethodInvocationHandlerFactory (org.glassfish.jersey.server.model.internal)
invoke:-1, ResourceMethodInvocationHandlerFactory$$Lambda$1810/0x00000008016a4440 (org.glassfish.jersey.server.model.internal)
run:146, AbstractJavaResourceMethodDispatcher$1 (org.glassfish.jersey.server.model.internal)
invoke:189, AbstractJavaResourceMethodDispatcher (org.glassfish.jersey.server.model.internal)
doDispatch:159, JavaResourceMethodDispatcherProvider$VoidOutInvoker (org.glassfish.jersey.server.model.internal)
dispatch:93, AbstractJavaResourceMethodDispatcher (org.glassfish.jersey.server.model.internal)
invoke:478, ResourceMethodInvoker (org.glassfish.jersey.server.model)
apply:400, ResourceMethodInvoker (org.glassfish.jersey.server.model)
apply:81, ResourceMethodInvoker (org.glassfish.jersey.server.model)
run:274, ServerRuntime$1 (org.glassfish.jersey.server)
call:248, Errors$1 (org.glassfish.jersey.internal)
call:244, Errors$1 (org.glassfish.jersey.internal)
process:292, Errors (org.glassfish.jersey.internal)
process:274, Errors (org.glassfish.jersey.internal)
process:244, Errors (org.glassfish.jersey.internal)
runInScope:266, RequestScope (org.glassfish.jersey.process.internal)

in the build.gradle file related entries are:

providedCompile 'org.hibernate:hibernate-validator:8.0.0.Final'
    providedCompile 'jakarta.validation:jakarta.validation-api:3.0.1'
    providedCompile 'fish.payara.api:payara-api:6.2023.9'
    providedCompile 'fish.payara.server.core.common:container-common:6.2023.8'
    providedCompile 'org.eclipse.persistence:org.eclipse.persistence.jpa:4.0.1.payara-p1'

    providedCompile 'org.glassfish.jersey.core:jersey-server:3.1.0.payara-p1'
    providedCompile 'org.glassfish.jersey.media:jersey-media-multipart:3.1.0.payara-p1'
    providedCompile 'org.glassfish.jersey.media:jersey-media-json-jackson:3.1.0.payara-p1'

The validated resource method looks like this:

@Path("/wallbuttons")
public class WallButtonResource extends SubResource {

    [....]

    @POST
    public void createWallButton(@Valid ApiWallButton entity,
                              
                                 @Suspended AsyncResponse asyncResponse) {
        final Long groupId = getGroupId(uriInfo);
        final UriBuilder ub = uriInfo.getAbsolutePathBuilder();
        final Authentication authentication = AuthenticationEvaluator.getCurrentAuthentication();

        ExceptionHandlingExecutor.executeRequestWithAuthentication(executorService, () -> {
            
                final Entity created = wallButtonService.create(entity);
                asyncResponse.resume(
                    Response.created(
                        UriHelper.createURLForApiRepresentation(ub, created)).entity(created)
                        .build()
                );
            

        }, asyncResponse, AuthenticationEvaluator.getCurrentAuthentication());
    }

And the validator

package at.ppcssolution.domain.nursecall.api.validation;

import at.ppcssolution.domain.nursecall.api.ApiWallButton;
import at.ppcssolution.domain.nursecall.api.IdentifierExtractor;
import at.ppcssolution.domain.nursecall.entity.WallButton;
import at.ppcssolution.domain.nursecall.entity.WallButton_;
import at.ppcssolution.domain.nursecall.service.WallButtonService;
import at.seres.filter.modelfilter.AttributeFilter;
import at.seres.filter.modelfilter.ModelFilter;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.transaction.Transactional;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;


@Constraint(validatedBy = {UniqueWallButton.UniqueIdentifierValidator.class})
@Target(value = {ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueWallButton {

    String message() default "{device.uniqueImei}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @ApplicationScoped
    class UniqueIdentifierValidator implements ConstraintValidator<UniqueWallButton, ApiWallButton> {

        private final WallButtonService wallButtonService;
        @Context
        private UriInfo uriInfo;

        public UniqueIdentifierValidator() {
            this.wallButtonService = CDI.current().select(WallButtonService.class).get();
        }

        @Override
        public void initialize(UniqueWallButton constraintAnnotation) {
        }

        @Override
        @Transactional
        public boolean isValid(ApiWallButton value, ConstraintValidatorContext context) {
            boolean hasMultipleIds = false;
            if(uriInfo != null) {
                final Optional<String> multipleIds = uriInfo.getQueryParameters().get("multipleIds").stream().findFirst();
                hasMultipleIds = Boolean.valueOf(multipleIds.orElse("false"));
            }
            List<String> identifiers = new ArrayList<>();
            if(hasMultipleIds) {
                final Set<String> uniqueIdentifiers = IdentifierExtractor.getUniqueIdentifiers(value.getIdentifier());
                identifiers.addAll(uniqueIdentifiers);
            } else {
                identifiers.add(value.getIdentifier());
            }
            List<ModelFilter> filters = new ArrayList<>();
            filters.add(AttributeFilter.filter(identifiers, WallButton_.identifier));
            final List<WallButton> buttonsWithSameIdentifier = wallButtonService.getAll(filters);
            boolean isValid;

            if (buttonsWithSameIdentifier.isEmpty()) {
                return true;
            } else if (buttonsWithSameIdentifier.size() == 1) {
                //If it is the same button - everything is cool
                isValid = Objects.equals(buttonsWithSameIdentifier.get(0).getId(), value.getId());
            } else {
                //more than one matching identifier -> this is definitely not valid
                isValid = false;
            }

            if (!isValid) {
                context.disableDefaultConstraintViolation();
                if(hasMultipleIds) {
                    HibernateConstraintValidatorContext hibernateConstraintValidatorContext =
                        context.unwrap( HibernateConstraintValidatorContext.class );
                    hibernateConstraintValidatorContext.addMessageParameter("usedIdentifiers",
                            buttonsWithSameIdentifier.stream().map(wallButton -> wallButton.getIdentifier()).sorted().collect(Collectors.toList()).toString());
                    hibernateConstraintValidatorContext
                            .buildConstraintViolationWithTemplate("{device.uniqueIdentifiers}")
                            .addPropertyNode("identifier")
                            .addConstraintViolation();
                } else {
                    context.buildConstraintViolationWithTemplate(context.
                                    getDefaultConstraintMessageTemplate())
                            .addPropertyNode("identifier")
                            .addConstraintViolation();
                }
            }

            return isValid;
        }



    }
}

Upvotes: 0

Views: 44

Answers (0)

Related Questions