You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2020/10/21 12:51:28 UTC

[cxf] branch master updated: Enable to pass a bval Validator to BeanValidationProvider (it is more… (#695)

This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 57f8847  Enable to pass a bval Validator to BeanValidationProvider (it is more… (#695)
57f8847 is described below

commit 57f88470951d981eccbe1f427cbe0398438f706d
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Wed Oct 21 14:50:44 2020 +0200

    Enable to pass a bval Validator to BeanValidationProvider (it is more… (#695)
    
    * Enable to pass a bval Validator to BeanValidationProvider (it is more than enough and faster than creating validators again and again) + enable to cache the fact no validation is needed
    
    * shouldValidateBean must call isBeanConstrained and not hasConstraints, thanks @reta
---
 .../validation/AbstractValidationInterceptor.java  |  19 +++-
 .../cxf/validation/BeanValidationProvider.java     | 116 ++++++++++++++++-----
 2 files changed, 108 insertions(+), 27 deletions(-)

diff --git a/core/src/main/java/org/apache/cxf/validation/AbstractValidationInterceptor.java b/core/src/main/java/org/apache/cxf/validation/AbstractValidationInterceptor.java
index 2f39152..f0e98d4 100644
--- a/core/src/main/java/org/apache/cxf/validation/AbstractValidationInterceptor.java
+++ b/core/src/main/java/org/apache/cxf/validation/AbstractValidationInterceptor.java
@@ -36,11 +36,13 @@ import org.apache.cxf.phase.AbstractPhaseInterceptor;
 import org.apache.cxf.service.invoker.FactoryInvoker;
 import org.apache.cxf.service.invoker.Invoker;
 
-public abstract class AbstractValidationInterceptor extends AbstractPhaseInterceptor< Message > {
+public abstract class AbstractValidationInterceptor extends AbstractPhaseInterceptor< Message >
+        implements AutoCloseable {
     protected static final Logger LOG = LogUtils.getL7dLogger(AbstractValidationInterceptor.class);
     protected static final ResourceBundle BUNDLE = BundleUtils.getBundle(AbstractValidationInterceptor.class);
 
     private Object serviceObject;
+    private boolean customProvider;
     private volatile BeanValidationProvider provider;
 
     public AbstractValidationInterceptor(String phase) {
@@ -56,6 +58,13 @@ public abstract class AbstractValidationInterceptor extends AbstractPhaseInterce
     }
 
     @Override
+    public void close() {
+        if (customProvider) {
+            provider.close();
+        }
+    }
+
+    @Override
     public void handleMessage(Message message) {
         final Object theServiceObject = getServiceObject(message);
         if (theServiceObject == null) {
@@ -125,7 +134,13 @@ public abstract class AbstractValidationInterceptor extends AbstractPhaseInterce
             if (prop != null) {
                 provider = (BeanValidationProvider)prop;
             } else {
-                provider = new BeanValidationProvider();
+                // don't create 2 validator factories and one not released!
+                synchronized (this) {
+                    if (provider == null) {
+                        provider = new BeanValidationProvider();
+                        customProvider = true;
+                    }
+                }
             }
         }
         return provider;
diff --git a/core/src/main/java/org/apache/cxf/validation/BeanValidationProvider.java b/core/src/main/java/org/apache/cxf/validation/BeanValidationProvider.java
index f105b1d..425832b 100644
--- a/core/src/main/java/org/apache/cxf/validation/BeanValidationProvider.java
+++ b/core/src/main/java/org/apache/cxf/validation/BeanValidationProvider.java
@@ -21,6 +21,9 @@ package org.apache.cxf.validation;
 import java.lang.reflect.Method;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
 import java.util.logging.Logger;
 
 import javax.validation.Configuration;
@@ -30,20 +33,28 @@ import javax.validation.ParameterNameProvider;
 import javax.validation.Validation;
 import javax.validation.ValidationException;
 import javax.validation.ValidationProviderResolver;
+import javax.validation.Validator;
 import javax.validation.ValidatorFactory;
 import javax.validation.executable.ExecutableValidator;
 import javax.validation.spi.ValidationProvider;
 
 import org.apache.cxf.common.logging.LogUtils;
 
-public class BeanValidationProvider {
+import static java.util.Collections.emptySet;
+
+public class BeanValidationProvider implements AutoCloseable {
     private static final Logger LOG = LogUtils.getL7dLogger(BeanValidationProvider.class);
 
-    private final ValidatorFactory factory;
+    private final Runnable close;
+    private final Supplier<Validator> factory;
+    private final RuntimeCache runtimeCache; // /!must only be created when a single factory is used
 
     public BeanValidationProvider() {
         try {
-            factory = Validation.buildDefaultValidatorFactory();
+            final ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
+            this.factory = vf::getValidator;
+            this.close = vf::close;
+            this.runtimeCache = new RuntimeCache();
         } catch (final ValidationException ex) {
             LOG.severe("Bean Validation provider can not be found, no validation will be performed");
             throw ex;
@@ -58,18 +69,34 @@ public class BeanValidationProvider {
         try {
             Configuration<?> factoryCfg = Validation.byDefaultProvider().configure();
             initFactoryConfig(factoryCfg, cfg);
-            factory = factoryCfg.buildValidatorFactory();
+            final ValidatorFactory vf = factoryCfg.buildValidatorFactory();
+            this.factory = vf::getValidator;
+            this.close = vf::close;
+            this.runtimeCache = new RuntimeCache();
         } catch (final ValidationException ex) {
             LOG.severe("Bean Validation provider can not be found, no validation will be performed");
             throw ex;
         }
     }
 
+    public BeanValidationProvider(Validator validator) {
+        if (validator == null) {
+            throw new NullPointerException("Validator is null");
+        }
+        this.factory = () -> validator;
+        this.close = () -> {
+        };
+        this.runtimeCache = new RuntimeCache();
+    }
+
     public BeanValidationProvider(ValidatorFactory factory) {
         if (factory == null) {
             throw new NullPointerException("Factory is null");
         }
-        this.factory = factory;
+        this.factory = factory::getValidator;
+        this.close = () -> {
+        };
+        this.runtimeCache = new RuntimeCache();
     }
 
     public BeanValidationProvider(ValidationProviderResolver resolver) {
@@ -91,7 +118,11 @@ public class BeanValidationProvider {
                 ? Validation.byProvider(providerType).providerResolver(resolver).configure()
                 : Validation.byDefaultProvider().providerResolver(resolver).configure();
             initFactoryConfig(factoryCfg, cfg);
-            factory = factoryCfg.buildValidatorFactory();
+            final ValidatorFactory vf = factoryCfg.buildValidatorFactory();
+            this.factory = vf::getValidator;
+            this.close = () -> {
+            };
+            this.runtimeCache = new RuntimeCache();
         } catch (final ValidationException ex) {
             LOG.severe("Bean Validation provider can not be found, no validation will be performed");
             throw ex;
@@ -111,46 +142,81 @@ public class BeanValidationProvider {
     }
 
     public< T > void validateParameters(final T instance, final Method method, final Object[] arguments) {
-
-        final ExecutableValidator methodValidator = getExecutableValidator();
-        final Set< ConstraintViolation< T > > violations = methodValidator.validateParameters(instance,
-            method, arguments);
-
-        if (!violations.isEmpty()) {
-            throw new ConstraintViolationException(violations);
+        final Validator validator = factory.get();
+        final ExecutableValidator methodValidator = validator.forExecutables();
+        if (runtimeCache == null || runtimeCache.shouldValidateParameters(validator, method)) {
+            final Set<ConstraintViolation<T>> violations = methodValidator.validateParameters(instance,
+                    method, arguments);
+            if (!violations.isEmpty()) {
+                throw new ConstraintViolationException(violations);
+            }
         }
     }
 
     public< T > void validateReturnValue(final T instance, final Method method, final Object returnValue) {
-        final ExecutableValidator methodValidator = getExecutableValidator();
-        final Set<ConstraintViolation< T > > violations = methodValidator.validateReturnValue(instance,
-            method, returnValue);
-
-        if (!violations.isEmpty()) {
-            throw new ResponseConstraintViolationException(violations);
+        final Validator validator = factory.get();
+        final ExecutableValidator methodValidator = validator.forExecutables();
+        if (runtimeCache == null || runtimeCache.shouldValidateReturnedValue(validator, method)) {
+            final Set<ConstraintViolation<T>> violations = methodValidator.validateReturnValue(instance,
+                    method, returnValue);
+            if (!violations.isEmpty()) {
+                throw new ResponseConstraintViolationException(violations);
+            }
         }
     }
 
     public< T > void validateReturnValue(final T bean) {
-        final Set<ConstraintViolation< T > > violations = doValidateBean(bean);
+        Validator validator = factory.get();
+        if (runtimeCache != null && bean != null
+                && !runtimeCache.shouldValidateBean(validator, bean.getClass())) {
+            return;
+        }
+        final Set<ConstraintViolation< T > > violations = doValidateBean(validator, bean);
         if (!violations.isEmpty()) {
             throw new ResponseConstraintViolationException(violations);
         }
     }
 
     public< T > void validateBean(final T bean) {
-        final Set<ConstraintViolation< T > > violations = doValidateBean(bean);
+        final Set<ConstraintViolation< T > > violations = doValidateBean(factory.get(), bean);
         if (!violations.isEmpty()) {
             throw new ConstraintViolationException(violations);
         }
     }
 
-    private< T > Set<ConstraintViolation< T > > doValidateBean(final T bean) {
-        return factory.getValidator().validate(bean);
+    private< T > Set<ConstraintViolation< T > > doValidateBean(final Validator validator, final T bean) {
+        if (validator.getConstraintsForClass(bean.getClass()).isBeanConstrained()) {
+            return validator.validate(bean);
+        }
+        return emptySet();
+    }
+
+    @Override
+    public void close() {
+        close.run();
     }
 
-    private ExecutableValidator getExecutableValidator() {
+    // only created when there is a single validator/factory so it is safe to cache
+    // note: the validator is passed as param to avoid to create useless ones
+    private static class RuntimeCache {
+        private final ConcurrentMap<Class<?>, Boolean> types = new ConcurrentHashMap<>();
+        private final ConcurrentMap<Method, Boolean> params = new ConcurrentHashMap<>();
+        private final ConcurrentMap<Method, Boolean> returnedValues = new ConcurrentHashMap<>();
+
+        public boolean shouldValidateParameters(final Validator validator, final Method method) {
+            return params.computeIfAbsent(method, m -> validator.getConstraintsForClass(m.getDeclaringClass())
+                    .getConstraintsForMethod(m.getName(), m.getParameterTypes())
+                    .hasConstrainedParameters());
+        }
 
-        return factory.getValidator().forExecutables();
+        public boolean shouldValidateReturnedValue(final Validator validator, final Method method) {
+            return returnedValues.computeIfAbsent(method, m -> validator.getConstraintsForClass(m.getDeclaringClass())
+                    .getConstraintsForMethod(m.getName(), method.getParameterTypes())
+                    .hasConstrainedReturnValue());
+        }
+
+        public boolean shouldValidateBean(final Validator validator, final Class<?> clazz) {
+            return types.computeIfAbsent(clazz, it -> validator.getConstraintsForClass(it).isBeanConstrained());
+        }
     }
 }