Skip to content

Commit fe083fa

Browse files
committed
HHH-14694 Test proxy class reuse between SessionFactories within same classloader
1 parent bd3cd84 commit fe083fa

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.proxy;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import org.hibernate.boot.MetadataSources;
10+
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
11+
import org.hibernate.boot.registry.StandardServiceRegistry;
12+
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
13+
import org.hibernate.boot.registry.classloading.internal.TcclLookupPrecedence;
14+
import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl;
15+
import org.hibernate.bytecode.spi.ByteCodeHelper;
16+
import org.hibernate.bytecode.spi.BytecodeProvider;
17+
import org.hibernate.engine.spi.SessionFactoryImplementor;
18+
import org.hibernate.engine.spi.SessionImplementor;
19+
import org.hibernate.testing.orm.junit.Jira;
20+
import org.hibernate.testing.util.ServiceRegistryUtil;
21+
import org.junit.jupiter.api.Test;
22+
23+
import java.io.InputStream;
24+
import java.util.Set;
25+
import java.util.function.Function;
26+
27+
import static org.junit.jupiter.api.Assertions.assertNotSame;
28+
import static org.junit.jupiter.api.Assertions.assertSame;
29+
30+
@Jira("https://hibernate.atlassian.net/browse/HHH-14694")
31+
public class ProxyClassReuseTest {
32+
33+
@Test
34+
void testReuse() {
35+
Function<SessionFactoryImplementor, Class<?>> proxyGetter = sf -> {
36+
try (SessionImplementor s = sf.openSession()) {
37+
return s.getReference( MyEntity.class, "abc" ).getClass();
38+
}
39+
};
40+
final BytecodeProvider bytecodeProvider = new BytecodeProviderImpl();
41+
Class<?> proxyClass1 = withFactory( proxyGetter, bytecodeProvider, null );
42+
Class<?> proxyClass2 = withFactory( proxyGetter, bytecodeProvider, null );
43+
assertSame( proxyClass1, proxyClass2 );
44+
}
45+
46+
@Test
47+
void testReuseWithDifferentFactories() {
48+
Function<SessionFactoryImplementor, Class<?>> proxyGetter = sf -> {
49+
try (SessionImplementor s = sf.openSession()) {
50+
return s.getReference( MyEntity.class, "abc" ).getClass();
51+
}
52+
};
53+
Class<?> proxyClass1 = withFactory( proxyGetter, null, null );
54+
Class<?> proxyClass2 = withFactory( proxyGetter, null, null );
55+
assertSame( proxyClass1, proxyClass2 );
56+
assertSame( proxyClass1.getClassLoader(), MyEntity.class.getClassLoader() );
57+
assertSame( proxyClass2.getClassLoader(), MyEntity.class.getClassLoader() );
58+
}
59+
60+
@Test
61+
void testNoReuse() {
62+
Function<SessionFactoryImplementor, Class<?>> proxyGetter = sf -> {
63+
try {
64+
//noinspection unchecked
65+
Function<SessionFactoryImplementor, Class<?>> getter = (Function<SessionFactoryImplementor, Class<?>>) Thread.currentThread()
66+
.getContextClassLoader()
67+
.loadClass( ProxyClassReuseTest.class.getName() + "$ProxyGetter" )
68+
.getConstructor()
69+
.newInstance();
70+
return getter.apply( sf );
71+
}
72+
catch (Exception ex) {
73+
throw new RuntimeException( ex );
74+
}
75+
};
76+
// Create two isolated class loaders that load the entity and proxy classes in isolation
77+
Set<String> isolatedClasses = Set.of( "org.hibernate.orm.test.proxy.*" );
78+
ClassLoader cl1 = new IsolatingClassLoader( isolatedClasses );
79+
ClassLoader cl2 = new IsolatingClassLoader( isolatedClasses );
80+
Class<?> proxyClass1 = withFactory( proxyGetter, null, cl1 );
81+
Class<?> proxyClass2 = withFactory( proxyGetter, null, cl2 );
82+
// The two proxy classes shall be defined on the respective isolated class loaders and hence be different
83+
assertNotSame( proxyClass1, proxyClass2 );
84+
assertSame( proxyClass1.getClassLoader(), cl1 );
85+
assertSame( proxyClass2.getClassLoader(), cl2 );
86+
}
87+
88+
<T> T withFactory(Function<SessionFactoryImplementor, T> consumer, BytecodeProvider bytecodeProvider, ClassLoader classLoader) {
89+
final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
90+
try {
91+
if (classLoader != null) {
92+
Thread.currentThread().setContextClassLoader( classLoader );
93+
}
94+
final BootstrapServiceRegistryBuilder bsr = new BootstrapServiceRegistryBuilder();
95+
bsr.applyTcclLookupPrecedence( TcclLookupPrecedence.BEFORE );
96+
final StandardServiceRegistryBuilder builder = ServiceRegistryUtil.serviceRegistryBuilder(bsr.build());
97+
if ( bytecodeProvider != null ) {
98+
builder.addService( BytecodeProvider.class, bytecodeProvider );
99+
}
100+
final StandardServiceRegistry ssr = builder.build();
101+
102+
try (final SessionFactoryImplementor sf = (SessionFactoryImplementor) new MetadataSources( ssr )
103+
.addAnnotatedClassName( ProxyClassReuseTest.class.getName() + "$MyEntity" )
104+
.buildMetadata()
105+
.getSessionFactoryBuilder()
106+
.build()) {
107+
return consumer.apply( sf );
108+
}
109+
catch (Exception e) {
110+
StandardServiceRegistryBuilder.destroy( ssr );
111+
throw e;
112+
}
113+
}
114+
finally {
115+
if (classLoader != null) {
116+
Thread.currentThread().setContextClassLoader( oldClassLoader );
117+
}
118+
}
119+
}
120+
121+
@Entity(name = "MyEntity")
122+
public static class MyEntity {
123+
@Id
124+
String id;
125+
String name;
126+
}
127+
128+
public static class ProxyGetter implements Function<SessionFactoryImplementor, Class<?>> {
129+
@Override
130+
public Class<?> apply(SessionFactoryImplementor sf) {
131+
try (SessionImplementor s = sf.openSession()) {
132+
return s.getReference( MyEntity.class, "abc" ).getClass();
133+
}
134+
}
135+
}
136+
137+
private static class IsolatingClassLoader extends ClassLoader {
138+
139+
private final Set<String> isolatedClasses;
140+
141+
public IsolatingClassLoader(Set<String> isolatedClasses) {
142+
super( IsolatingClassLoader.class.getClassLoader() );
143+
this.isolatedClasses = isolatedClasses;
144+
}
145+
146+
@Override
147+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
148+
synchronized (getClassLoadingLock(name)) {
149+
Class<?> c = findLoadedClass(name);
150+
if (c == null) {
151+
if (isIsolatedClass(name)) {
152+
InputStream is = this.getResourceAsStream( name.replace( '.', '/' ) + ".class" );
153+
if ( is == null ) {
154+
throw new ClassNotFoundException( name + " not found" );
155+
}
156+
157+
try {
158+
byte[] bytecode = ByteCodeHelper.readByteCode( is );
159+
return defineClass( name, bytecode, 0, bytecode.length );
160+
}
161+
catch( Throwable t ) {
162+
throw new ClassNotFoundException( name + " not found", t );
163+
}
164+
} else {
165+
// Parent first
166+
c = super.loadClass(name, resolve);
167+
}
168+
}
169+
return c;
170+
}
171+
}
172+
173+
private boolean isIsolatedClass(String name) {
174+
if (isolatedClasses != null) {
175+
for (String isolated : isolatedClasses) {
176+
if (isolated.endsWith(".*")) {
177+
String isolatedPackage = isolated.substring(0, isolated.length() - 1);
178+
String paramPackage = name.substring(0, name.lastIndexOf('.') + 1);
179+
if (paramPackage.startsWith(isolatedPackage)) {
180+
// Matching package
181+
return true;
182+
}
183+
} else if (isolated.equals(name)) {
184+
return true;
185+
}
186+
}
187+
}
188+
return false;
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)