Skip to content
Permalink
Browse files
BVAL-170 perf boost for CDI integrations startup
  • Loading branch information
rmannibucau committed Apr 11, 2019
1 parent 8b683b9 commit 5bca3a73ae798d0671f964448b6e590d9bae396d
Showing 1 changed file with 87 additions and 45 deletions.
@@ -18,18 +18,22 @@
*/
package org.apache.bval.cdi;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
@@ -41,14 +45,14 @@
import javax.enterprise.inject.spi.ProcessBean;
import javax.validation.BootstrapConfiguration;
import javax.validation.Configuration;
import javax.validation.Constraint;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableType;
import javax.validation.executable.ValidateOnExecution;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.MethodType;

import org.apache.bval.jsr.ConfigurationImpl;
import org.apache.bval.jsr.util.ExecutableTypes;
@@ -75,11 +79,13 @@ public static void setAnnotatedTypeFilter(AnnotatedTypeFilter annotatedTypeFilte

private final Configuration<?> config;
private Lazy<ValidatorFactory> factory;
private Lazy<Validator> validator;

private Set<ExecutableType> globalExecutableTypes;
private boolean isExecutableValidationEnabled;

private final Collection<Class<?>> potentiallyBValAnnotation = new HashSet<>();
private final Collection<Class<?>> notBValAnnotation = new HashSet<>();

public BValExtension() { // read the config, could be done in a quicker way but this let us get defaults without duplicating code
config = Validation.byDefaultProvider().configure();
try {
@@ -97,21 +103,6 @@ public BValExtension() { // read the config, could be done in a quicker way but
}
}

// lazily to get a small luck to have CDI in place
private void ensureFactoryValidator() {
if (validator != null) {
return;
}
if (config instanceof ConfigurationImpl) {
// ignore parts of the config relying on CDI since we didn't start yet
((ConfigurationImpl) config).deferBootstrapOverrides();
}
if (factory == null) {
factory = new Lazy<>(config::buildValidatorFactory);
}
validator = new Lazy<>(() -> factory.get().getValidator());
}

public Set<ExecutableType> getGlobalExecutableTypes() {
return globalExecutableTypes;
}
@@ -133,43 +124,27 @@ public <A> void processAnnotatedType(final @Observes ProcessAnnotatedType<A> pat
}
final Class<A> javaClass = annotatedType.getJavaClass();
final int modifiers = javaClass.getModifiers();
if (!javaClass.isInterface() && !Modifier.isFinal(modifiers) && !Modifier.isAbstract(modifiers)) {
if (!javaClass.isInterface() && !javaClass.isAnonymousClass() && !Modifier.isFinal(modifiers) && !Modifier.isAbstract(modifiers)) {
try {
ensureFactoryValidator();
try {
final BeanDescriptor classConstraints = validator.get().getConstraintsForClass(javaClass);

final boolean validConstructors = globalExecutableTypes.contains(ExecutableType.CONSTRUCTORS)
&& !classConstraints.getConstrainedConstructors().isEmpty();
final boolean validBusinessMethods =
globalExecutableTypes.contains(ExecutableType.NON_GETTER_METHODS)
&& !classConstraints.getConstrainedMethods(MethodType.NON_GETTER).isEmpty();
final boolean validGetterMethods = globalExecutableTypes.contains(ExecutableType.GETTER_METHODS)
&& !classConstraints.getConstrainedMethods(MethodType.GETTER).isEmpty();

if (annotatedType.isAnnotationPresent(ValidateOnExecution.class)
|| hasValidationAnnotation(annotatedType.getMethods())
|| hasValidationAnnotation(annotatedType.getConstructors()) || validConstructors
|| validBusinessMethods || validGetterMethods) {
pat.setAnnotatedType(new BValAnnotatedType<>(annotatedType));
}
} catch (final NoClassDefFoundError ncdfe) {
// skip
if (hasValidation(annotatedType)
|| hasValidationAnnotation(annotatedType.getMethods())
|| hasValidationAnnotation(annotatedType.getConstructors())
|| Stream.concat(annotatedType.getMethods().stream(), annotatedType.getConstructors().stream())
.flatMap(it -> it.getParameters().stream())
.anyMatch(this::hasValidation)) {
pat.setAnnotatedType(new BValAnnotatedType<>(annotatedType));
}
} catch (final Exception e) {
if (e instanceof ValidationException) {
throw e;
}
LOGGER.log(Level.INFO, e.getMessage());
} catch (final NoClassDefFoundError ncdfe) {
// skip
}
}
}

private static <A> boolean hasValidationAnnotation(
final Collection<? extends AnnotatedCallable<? super A>> methods) {
return methods.stream().anyMatch(m -> m.isAnnotationPresent(ValidateOnExecution.class));
}

public <A> void processBean(final @Observes ProcessBean<A> processBeanEvent) {
if (validatorFound && validatorFactoryFound) {
return;
@@ -219,6 +194,73 @@ public void addBValBeans(final @Observes AfterBeanDiscovery afterBeanDiscovery,
}
}

public void afterStart(@Observes final AfterDeploymentValidation clearEvent) {
potentiallyBValAnnotation.clear();
notBValAnnotation.clear();
}

private boolean hasValidationAnnotation(final Collection<? extends Annotated> annotateds) {
return annotateds.stream().anyMatch(this::hasValidation);
}

private boolean hasValidation(final Annotated m) {
return m.getAnnotations().stream()
.anyMatch(it -> {
final Class<? extends Annotation> type = it.annotationType();
if (type == ValidateOnExecution.class || type == Valid.class) {
return true;
}
if (isSkippedAnnotation(type)) {
return false;
}
if (type.getName().startsWith("javax.validation.constraints")) {
return true;
}
if (notBValAnnotation.contains(type)) { // more likely so faster first
return false;
}
if (potentiallyBValAnnotation.contains(type)) {
return true;
}
cacheIsBvalAnnotation(type);
return potentiallyBValAnnotation.contains(type);
});
}

private boolean isSkippedAnnotation(final Class<? extends Annotation> type) {
if (type.getName().startsWith("java.")) {
return true;
}
if (type.getName().startsWith("javax.enterprise.")) {
return true;
}
if (type.getName().startsWith("javax.inject.")) {
return true;
}
return false;
}

private void cacheIsBvalAnnotation(final Class<? extends Annotation> type) {
if (flattenAnnotations(type, new HashSet<>()).anyMatch(it -> it == Constraint.class)) {
potentiallyBValAnnotation.add(type);
} else {
notBValAnnotation.add(type);
}
}

private Stream<Class<?>> flattenAnnotations(final Class<? extends Annotation> type, final Set<Class<?>> seen) {
seen.add(type);
return Stream.of(type)
.flatMap(it -> Stream.concat(
Stream.of(it),
Stream.of(it.getAnnotations())
.map(Annotation::annotationType)
.distinct()
.filter(a -> !isSkippedAnnotation(a))
.filter(seen::add)
.flatMap(a -> flattenAnnotations(a, seen))));
}

/**
* Request that an instance of the specified type be provided by the container.
* @param clazz

0 comments on commit 5bca3a7

Please sign in to comment.