customDisabledReason) {
+ this.annotationType = annotationType;
+ this.enabledReason = enabledReason;
+ this.disabledReason = disabledReason;
+ this.customDisabledReason = customDisabledReason;
+ }
+
+ abstract boolean isEnabled(A annotation);
+
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
+ return findAnnotation(context.getElement(), annotationType) //
+ .map(annotation -> isEnabled(annotation) ? enabled(enabledReason)
+ : disabled(disabledReason, customDisabledReason.apply(annotation))) //
+ .orElseGet(this::enabledByDefault);
+ }
+
+ private ConditionEvaluationResult enabledByDefault() {
+ String reason = String.format("@%s is not present", annotationType.getSimpleName());
+ return enabled(reason);
+ }
+
+}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate61.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java
similarity index 50%
rename from spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate61.java
rename to spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java
index 451eb8866..0ee4e2598 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate61.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate.java
@@ -23,14 +23,31 @@ import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
- * Annotation to flag JUnit 5 test cases to ONLY activate when Hibernate 6.2 is on the classpath.
+ * {@code @DisabledOnHibernate} is used to signal that the annotated test class or test method is only disabled
+ * if the given Hibernate {@linkplain #value version} is being used.
*
* @author Greg Turnquist
- * @since 3.1
+ * @author Mark Paluch
+ * @since 3.2
*/
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
-@ExtendWith(HibernateSupport.DisabledWhenHibernate61OnClasspath.class)
-public @interface DisabledOnHibernate61 {
+@ExtendWith(DisabledOnHibernateCondition.class)
+public @interface DisabledOnHibernate {
+ /**
+ * The version of Hibernate to disable the test or container case on. The version specifier can hold individual
+ * version components matching effectively the version in a prefix-manner. The more specific you want to match, the
+ * more version components you can specify, such as {@code 6.2.1} to match a specific service release or {@code 6} to
+ * match a full major version.
+ */
+ String value();
+
+ /**
+ * Custom reason to provide if the test or container is disabled.
+ *
+ * If a custom reason is supplied, it will be combined with the default reason for this annotation. If a custom reason
+ * is not supplied, the default reason will be used.
+ */
+ String disabledReason() default "";
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate62.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate62.java
deleted file mode 100644
index b39f7000d..000000000
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernate62.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2015-2024 the original author or authors.
- *
- * Licensed 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
- *
- * https://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.springframework.data.jpa.util;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.junit.jupiter.api.extension.ExtendWith;
-
-/**
- * Annotation to flag JUnit 5 test cases to ONLY activate when Hibernate 6.1 is on the classpath.
- *
- * @author Greg Turnquist
- * @since 3.1
- */
-@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-@ExtendWith(HibernateSupport.DisabledWhenHibernate62OnClasspath.class)
-public @interface DisabledOnHibernate62 {
-
-}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java
new file mode 100644
index 000000000..804309544
--- /dev/null
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateCondition.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.data.jpa.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.extension.ExecutionCondition;
+
+/**
+ * {@link ExecutionCondition} for {@link DisabledOnHibernate @DisabledOnHibernate}.
+ *
+ * @see DisabledOnHibernate
+ */
+class DisabledOnHibernateCondition extends BooleanExecutionCondition {
+
+ static final String ENABLED_ON_CURRENT_HIBERNATE = //
+ "Enabled on Hibernate version: " + org.hibernate.Version.getVersionString();
+
+ static final String DISABLED_ON_CURRENT_HIBERNATE = //
+ "Disabled on Hibernate version: " + org.hibernate.Version.getVersionString();
+
+ DisabledOnHibernateCondition() {
+ super(DisabledOnHibernate.class, ENABLED_ON_CURRENT_HIBERNATE, DISABLED_ON_CURRENT_HIBERNATE,
+ DisabledOnHibernate::disabledReason);
+ }
+
+ @Override
+ boolean isEnabled(DisabledOnHibernate annotation) {
+
+ VersionMatcher disabled = VersionMatcher.parse(annotation.value());
+ VersionMatcher hibernate = VersionMatcher.parse(org.hibernate.Version.getVersionString());
+
+ return !disabled.matches(hibernate);
+ }
+
+ static class VersionMatcher {
+
+ private static final Pattern PATTERN = Pattern.compile("(\\d+)+");
+ private final int[] components;
+
+ private VersionMatcher(int[] components) {
+ this.components = components;
+ }
+
+ /**
+ * Parse the given version string into a {@link VersionMatcher}.
+ *
+ * @param version
+ * @return
+ */
+ public static VersionMatcher parse(String version) {
+
+ Matcher matcher = PATTERN.matcher(version);
+ List ints = new ArrayList<>();
+ while (matcher.find()) {
+ ints.add(Integer.parseInt(matcher.group()));
+ }
+
+ return new VersionMatcher(ints.stream().mapToInt(value -> value).toArray());
+ }
+
+ /**
+ * Match the given version against another VersionMatcher. This matcher's version spec controls the expected length.
+ * If the other version is shorter, then the match returns {@code false}.
+ *
+ * @param version
+ * @return
+ */
+ public boolean matches(VersionMatcher version) {
+
+ for (int i = 0; i < components.length; i++) {
+ if (version.components.length <= i) {
+ return false;
+ }
+ if (components[i] != version.components[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java
new file mode 100644
index 000000000..15a977cb5
--- /dev/null
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/DisabledOnHibernateConditionTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.data.jpa.util;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.springframework.data.jpa.util.DisabledOnHibernateCondition.*;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link DisabledOnHibernate}.
+ *
+ * @author Mark Paluch
+ */
+class DisabledOnHibernateConditionTests {
+
+ @Test // GH-3081
+ void shouldMatchVersions() {
+
+ VersionMatcher spec = VersionMatcher.parse("1.2");
+ VersionMatcher lib = VersionMatcher.parse("v1.2.3.4.Final");
+
+ assertThat(spec.matches(lib)).isTrue();
+ }
+
+ @Test // GH-3081
+ void shouldNotMatchVersions() {
+
+ VersionMatcher spec = VersionMatcher.parse("1.2");
+ VersionMatcher lib = VersionMatcher.parse("2.2.3.4");
+
+ assertThat(spec.matches(lib)).isFalse();
+ }
+
+ @Test // GH-3081
+ void shouldNotMatchVersionsWithExceedingLength() {
+
+ VersionMatcher spec = VersionMatcher.parse("1.2.3.4");
+ VersionMatcher lib = VersionMatcher.parse("1.2");
+
+ assertThat(spec.matches(lib)).isFalse();
+ }
+}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HibernateSupport.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HibernateSupport.java
deleted file mode 100644
index d2b325354..000000000
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/HibernateSupport.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2015-2024 the original author or authors.
- *
- * Licensed 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
- *
- * https://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.springframework.data.jpa.util;
-
-import org.junit.jupiter.api.extension.ConditionEvaluationResult;
-import org.junit.jupiter.api.extension.ExecutionCondition;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.springframework.util.ClassUtils;
-
-/**
- * JUnit 5 utilities to support conditional test cases based upon Hibernate classpath settings.
- *
- * @author Greg Turnquist
- * @since 3.1
- */
-abstract class HibernateSupport {
-
- /**
- * {@literal org.hibernate.dialect.PostgreSQL91Dialect} is deprecated in Hibernate 6.1 and fully removed in Hibernate
- * 6.2, making it a perfect detector between the two.
- */
- private static final boolean HIBERNATE_61_ON_CLASSPATH = ClassUtils
- .isPresent("org.hibernate.dialect.PostgreSQL91Dialect", HibernateSupport.class.getClassLoader());
-
- private static final boolean HIBERNATE_62_ON_CLASSPATH = !HIBERNATE_61_ON_CLASSPATH;
-
- static class DisabledWhenHibernate61OnClasspath implements ExecutionCondition {
-
- @Override
- public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext extensionContext) {
-
- return HibernateSupport.HIBERNATE_61_ON_CLASSPATH
- ? ConditionEvaluationResult.disabled("Disabled because Hibernate 6.1 is on the classpath")
- : ConditionEvaluationResult.enabled("NOT disabled because Hibernate 6.2 is on the classpath");
- }
- }
-
- static class DisabledWhenHibernate62OnClasspath implements ExecutionCondition {
-
- @Override
- public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext extensionContext) {
-
- return HibernateSupport.HIBERNATE_62_ON_CLASSPATH
- ? ConditionEvaluationResult.disabled("Disabled because Hibernate 6.2 is on the classpath")
- : ConditionEvaluationResult.enabled("NOT disabled because Hibernate 6.1 is on the classpath");
- }
-
- }
-}