Skip to content

Commit e6816f5

Browse files
committed
HHH-14694 Test proxy class reuse between SessionFactories within same classloader
1 parent 4438ada commit e6816f5

File tree

1 file changed

+193
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)