This is an automated email from the ASF dual-hosted git repository.
jungm pushed a commit to branch bval-3.0.x
in repository https://gitbox.apache.org/repos/asf/bval.git
The following commit(s) were added to refs/heads/bval-3.0.x by this push:
new e21d638 BVAL-222 - enable BValInterceptor when constraints are only
placed on parent classes
e21d638 is described below
commit e21d638afa5ac56ee94f0941fabab18e6d416549
Author: Markus Jung <[email protected]>
AuthorDate: Sat Mar 8 20:29:35 2025 +0100
BVAL-222 - enable BValInterceptor when constraints are only placed on
parent classes
cherry picked from eae0a94323265c0a0f3f099ffad0c6ec7469bd83
---
bval-jsr/pom.xml | 19 ++++
.../java/org/apache/bval/cdi/BValExtension.java | 108 ++++++++++++++-------
.../cdi/CdiConstraintOnlyOnParentClassTest.java | 75 ++++++++++++++
bval-tck/pom.xml | 12 +--
pom.xml | 4 +
5 files changed, 177 insertions(+), 41 deletions(-)
diff --git a/bval-jsr/pom.xml b/bval-jsr/pom.xml
index 55c5c63..93ec67a 100644
--- a/bval-jsr/pom.xml
+++ b/bval-jsr/pom.xml
@@ -224,6 +224,25 @@
</exclusion>
</exclusions>
</dependency>
+
+ <dependency>
+ <groupId>org.jboss.arquillian.junit</groupId>
+ <artifactId>arquillian-junit-container</artifactId>
+ <scope>test</scope>
+ <version>${version.arquillian}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.openwebbeans.arquillian</groupId>
+ <artifactId>owb-arquillian-standalone</artifactId>
+ <version>${version.owb}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.openwebbeans</groupId>
+ <artifactId>openwebbeans-impl</artifactId>
+ <version>${version.owb}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/bval-jsr/src/main/java/org/apache/bval/cdi/BValExtension.java
b/bval-jsr/src/main/java/org/apache/bval/cdi/BValExtension.java
index 9943ca4..20467eb 100644
--- a/bval-jsr/src/main/java/org/apache/bval/cdi/BValExtension.java
+++ b/bval-jsr/src/main/java/org/apache/bval/cdi/BValExtension.java
@@ -19,11 +19,17 @@
package org.apache.bval.cdi;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Executable;
import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -33,10 +39,8 @@ import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.AfterDeploymentValidation;
-import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.AnnotatedType;
import jakarta.enterprise.inject.spi.Bean;
-import jakarta.enterprise.inject.spi.BeanAttributes;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
import jakarta.enterprise.inject.spi.CDI;
@@ -66,7 +70,7 @@ import org.apache.bval.util.Validate;
public class BValExtension implements Extension {
private static final Logger LOGGER =
Logger.getLogger(BValExtension.class.getName());
- private static final AnnotatedTypeFilter DEFAULT_ANNOTATED_TYPE_FILTER =
+ public static final AnnotatedTypeFilter DEFAULT_ANNOTATED_TYPE_FILTER =
annotatedType ->
!annotatedType.getJavaClass().getName().startsWith("org.apache.bval.");
private static AnnotatedTypeFilter annotatedTypeFilter =
DEFAULT_ANNOTATED_TYPE_FILTER;
@@ -128,13 +132,28 @@ public class BValExtension implements Extension {
final int modifiers = javaClass.getModifiers();
if (!javaClass.isInterface() && !javaClass.isAnonymousClass() &&
!Modifier.isFinal(modifiers) && !Modifier.isAbstract(modifiers)) {
try {
- 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));
+ Queue<Class<?>> toProcess = new LinkedList<>();
+ toProcess.add(annotatedType.getJavaClass());
+
+ while (!toProcess.isEmpty()) {
+ Class<?> now = toProcess.poll();
+ Executable[] methods = now.getMethods();
+ Executable[] constructors = now.getConstructors();
+
+ if (hasValidation(now)
+ || hasValidation(methods) ||
hasValidation(constructors)
+ || hasParamsWithValidation(methods) ||
hasParamsWithValidation(constructors)) {
+ pat.setAnnotatedType(new
BValAnnotatedType<>(annotatedType));
+
+ break;
+ }
+
+ // Nothing found, collect superclass/interface and repeat
(See BVAL-222)
+ if (now.getSuperclass() != Object.class) {
+ toProcess.add(now.getSuperclass());
+ }
+
+ toProcess.addAll(Arrays.asList(now.getInterfaces()));
}
} catch (final Exception e) {
if (e instanceof ValidationException) {
@@ -201,32 +220,53 @@ public class BValExtension implements Extension {
notBValAnnotation.clear();
}
- private boolean hasValidationAnnotation(final Collection<? extends
Annotated> annotateds) {
- return annotateds.stream().anyMatch(this::hasValidation);
+ private boolean hasValidation(final AnnotatedElement[] elements) {
+ for (AnnotatedElement element : elements) {
+ if (hasValidation(element)) {
+ return true;
+ }
+ }
+
+ return false;
}
- 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("jakarta.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 hasParamsWithValidation(Executable[] executables) {
+ for (Executable executable : executables) {
+ for (Parameter param : executable.getParameters()) {
+ if (hasValidation(param)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean hasValidation(final AnnotatedElement element) {
+ for (Annotation annotation : element.getAnnotations()) {
+ final Class<? extends Annotation> type =
annotation.annotationType();
+ if (type == ValidateOnExecution.class || type == Valid.class) {
+ return true;
+ }
+ if (isSkippedAnnotation(type)) {
+ continue;
+ }
+ if (type.getName().startsWith("jakarta.validation.constraints")) {
+ return true;
+ }
+ if (notBValAnnotation.contains(type)) { // more likely so faster
first
+ continue;
+ }
+ if (potentiallyBValAnnotation.contains(type)) {
+ return true;
+ }
+ cacheIsBvalAnnotation(type);
+ if (potentiallyBValAnnotation.contains(type)) {
+ return true;
+ }
+ }
+
+ return false;
}
private boolean isSkippedAnnotation(final Class<? extends Annotation>
type) {
diff --git
a/bval-jsr/src/test/java/org/apache/bval/jsr/cdi/CdiConstraintOnlyOnParentClassTest.java
b/bval-jsr/src/test/java/org/apache/bval/jsr/cdi/CdiConstraintOnlyOnParentClassTest.java
new file mode 100644
index 0000000..1713a6e
--- /dev/null
+++
b/bval-jsr/src/test/java/org/apache/bval/jsr/cdi/CdiConstraintOnlyOnParentClassTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.cdi;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.constraints.NotNull;
+import org.apache.bval.cdi.BValExtension;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+
+@RunWith(Arquillian.class)
+public class CdiConstraintOnlyOnParentClassTest {
+ @ClassRule
+ public static ExternalResource allowMyServiceImplType = new
ExternalResource() {
+ @Override
+ protected void before() throws Throwable {
+ BValExtension.setAnnotatedTypeFilter(at -> at.getJavaClass() ==
GreetingServiceImpl.class);
+ }
+
+ @Override
+ protected void after() {
+
BValExtension.setAnnotatedTypeFilter(BValExtension.DEFAULT_ANNOTATED_TYPE_FILTER);
+ }
+ };
+
+ @Inject
+ private GreetingService service;
+
+ @Deployment
+ public static JavaArchive createDeployment() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(GreetingService.class, GreetingServiceImpl.class);
+ }
+
+ @Test
+ public void validationFail() {
+ Assert.assertThrows(ConstraintViolationException.class, () ->
service.greet(null));
+ }
+
+ public interface GreetingService {
+ void greet(@NotNull String name);
+ }
+
+ @ApplicationScoped
+ public static class GreetingServiceImpl implements GreetingService {
+ @Override
+ public void greet(String name) {
+ }
+ }
+}
diff --git a/bval-tck/pom.xml b/bval-tck/pom.xml
index 8a0b9d5..985dab5 100644
--- a/bval-tck/pom.xml
+++ b/bval-tck/pom.xml
@@ -32,8 +32,6 @@ under the License.
<properties>
<tck.version>3.0.1</tck.version>
- <owb.version>4.0.2</owb.version>
- <arquillian.version>1.8.0.Final</arquillian.version>
<validation.provider>org.apache.bval.jsr.ApacheValidationProvider</validation.provider>
</properties>
@@ -115,18 +113,18 @@ under the License.
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-container-test-spi</artifactId>
- <version>${arquillian.version}</version>
+ <version>${version.arquillian}</version>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans.arquillian</groupId>
<artifactId>owb-arquillian-standalone</artifactId>
- <version>${owb.version}</version>
+ <version>${version.owb}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
- <version>${owb.version}</version>
+ <version>${version.owb}</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -144,13 +142,13 @@ under the License.
<dependency>
<groupId>org.jboss.arquillian.testng</groupId>
<artifactId>arquillian-testng-container</artifactId>
- <version>${arquillian.version}</version>
+ <version>${version.arquillian}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet-jakarta</artifactId>
- <version>${arquillian.version}</version>
+ <version>${version.arquillian}</version>
<scope>test</scope>
</dependency>
diff --git a/pom.xml b/pom.xml
index f57ff48..866b62c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,10 @@
<site.server>bval.site</site.server>
<built.by>${user.name}</built.by>
<plugin.javadoc.version>3.6.3</plugin.javadoc.version>
+
+ <!-- testing dependencies -->
+ <version.owb>4.0.3</version.owb>
+ <version.arquillian>1.8.0.Final</version.arquillian>
</properties>
<inceptionYear>2010</inceptionYear>