Skip to content

Commit 7c5cf58

Browse files
committed
HHH-19884 Custom @ClassTemplate lifecycle annotations and extension
1 parent 23d12a6 commit 7c5cf58

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.orm.junit;
6+
7+
import java.lang.annotation.ElementType;
8+
import java.lang.annotation.Retention;
9+
import java.lang.annotation.RetentionPolicy;
10+
import java.lang.annotation.Target;
11+
12+
/**
13+
* Similar to JUnit's {@link org.junit.jupiter.api.AfterAll} but called after
14+
* each template invocation in a {@link @ClassTemplate} test class.
15+
*/
16+
@Target(ElementType.METHOD)
17+
@Retention(RetentionPolicy.RUNTIME)
18+
public @interface AfterClassTemplate {
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.orm.junit;
6+
7+
import java.lang.annotation.ElementType;
8+
import java.lang.annotation.Retention;
9+
import java.lang.annotation.RetentionPolicy;
10+
import java.lang.annotation.Target;
11+
12+
/**
13+
* Similar to JUnit's {@link org.junit.jupiter.api.BeforeAll} but called before
14+
* each template invocation in a {@link @ClassTemplate} test class.
15+
*/
16+
@Target(ElementType.METHOD)
17+
@Retention(RetentionPolicy.RUNTIME)
18+
public @interface BeforeClassTemplate {
19+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.orm.junit;
6+
7+
import org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback;
8+
import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback;
9+
import org.junit.jupiter.api.extension.ExtensionContext;
10+
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.lang.reflect.Method;
13+
14+
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods;
15+
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
16+
import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP;
17+
18+
/**
19+
* JUnit extension to provide hooks for methods annotated with {@link BeforeClassTemplate}
20+
* and {@link AfterClassTemplate} to be called before and after each class template invocation
21+
* <p>
22+
* Provides native support for methods with zero parameters or a single parameter typed either
23+
* as {@link EntityManagerFactoryScope} or {@link SessionFactoryScope}, for easy integration
24+
* with the {@link EntityManagerFactoryExtension entity manager} and {@link SessionFactoryExtension
25+
* session factory} extensions.
26+
*/
27+
public class ClassTemplateInvocationListenersExtension
28+
implements BeforeClassTemplateInvocationCallback, AfterClassTemplateInvocationCallback {
29+
@Override
30+
public void beforeClassTemplateInvocation(ExtensionContext context) throws Exception {
31+
final var testClass = context.getRequiredTestClass();
32+
final var annotatedMethods = findAnnotatedMethods( testClass, BeforeClassTemplate.class, BOTTOM_UP );
33+
for ( final var method : annotatedMethods ) {
34+
invokeLifecycleMethod( method, context.getRequiredTestInstance(), context );
35+
}
36+
}
37+
38+
@Override
39+
public void afterClassTemplateInvocation(ExtensionContext context) throws Exception {
40+
final var testClass = context.getRequiredTestClass();
41+
final var annotatedMethods = findAnnotatedMethods( testClass, AfterClassTemplate.class, BOTTOM_UP );
42+
for ( final var method : annotatedMethods ) {
43+
invokeLifecycleMethod( method, context.getRequiredTestInstance(), context );
44+
}
45+
}
46+
47+
private static void invokeLifecycleMethod(Method method, Object testInstance, ExtensionContext context)
48+
throws InvocationTargetException, IllegalAccessException {
49+
method.setAccessible( true );
50+
final var parameterTypes = method.getParameterTypes();
51+
if ( parameterTypes.length == 1 ) {
52+
final var parameterType = parameterTypes[0];
53+
if ( EntityManagerFactoryScope.class.isAssignableFrom( parameterType ) ) {
54+
final var scope = EntityManagerFactoryExtension.findEntityManagerFactoryScope(
55+
context.getRequiredTestInstance(),
56+
findAnnotation( context.getRequiredTestClass(), Jpa.class ),
57+
context
58+
);
59+
method.invoke( testInstance, scope );
60+
}
61+
else if ( SessionFactoryScope.class.isAssignableFrom( parameterType ) ) {
62+
final var sessionFactoryScope = SessionFactoryExtension.findSessionFactoryScope(
63+
context.getRequiredTestInstance(),
64+
context
65+
);
66+
method.invoke( testInstance, sessionFactoryScope );
67+
}
68+
else {
69+
throw new IllegalStateException(
70+
"ClassTemplate lifecycle method '" + method.getName() + "' has an unsupported parameter type: " + parameterType.getName()
71+
);
72+
}
73+
}
74+
else if ( parameterTypes.length == 0 ) {
75+
method.invoke( testInstance );
76+
}
77+
else {
78+
throw new IllegalStateException(
79+
"ClassTemplate lifecycle method '" + method.getName() + "' can only have zero or one parameter"
80+
);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)