From 726305f33ef11f2f3b6b2a08352da3cc392df5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Tabin?= Date: Sat, 19 Nov 2016 20:18:54 +0100 Subject: [PATCH] New setting "hibernate.classloader.tccl_lookup" to allow the configuration of the thread context classloader lookup. The bootstrap classloader context is not stored anymore in the ClassLoaderService because on Glassfish 4.1.1, the former will be closed after bootstrap, causing huge warning and stacktraces occurs in the log each time a HQL query has to be compiled. See ticket HHH-11245 for details. --- .../BootstrapServiceRegistryBuilder.java | 13 +- .../internal/ClassLoaderServiceImpl.java | 113 ++++++++++++---- .../classloading/spi/ClassLoaderService.java | 29 +++++ .../org/hibernate/cfg/AvailableSettings.java | 9 ++ .../EntityManagerFactoryBuilderImpl.java | 11 ++ .../internal/ClassLoaderServiceImplTest.java | 121 ++++++++++++++++++ 6 files changed, 269 insertions(+), 27 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java index a890004682..cb799ee566 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/BootstrapServiceRegistryBuilder.java @@ -14,6 +14,7 @@ import java.util.Set; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService.TCCLLookupBehavior; import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl; import org.hibernate.boot.registry.selector.StrategyRegistration; import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; @@ -39,6 +40,7 @@ public class BootstrapServiceRegistryBuilder { private List providedClassLoaders; private ClassLoaderService providedClassLoaderService; private StrategySelectorBuilder strategySelectorBuilder = new StrategySelectorBuilder(); + private TCCLLookupBehavior tcclLookupBehaviour = TCCLLookupBehavior.AFTER; private boolean autoCloseRegistry = true; @@ -85,6 +87,15 @@ public class BootstrapServiceRegistryBuilder { return this; } + /** + * Defines when the lookup in the thread context {@code ClassLoader} is done. + * + * @param behavior The behavior. + */ + public void applyTCCLBehavior(TCCLLookupBehavior behavior) { + tcclLookupBehaviour = behavior; + } + /** * @deprecated Use {@link #applyClassLoaderService} instead */ @@ -205,6 +216,7 @@ public class BootstrapServiceRegistryBuilder { } classLoaderService = new ClassLoaderServiceImpl( classLoaders ); + classLoaderService.setTCCLLookupBehavior( tcclLookupBehaviour ); } else { classLoaderService = providedClassLoaderService; @@ -215,7 +227,6 @@ public class BootstrapServiceRegistryBuilder { classLoaderService ); - return new BootstrapServiceRegistryImpl( autoCloseRegistry, classLoaderService, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java index 26dade5d3f..1e2ba1f1bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImpl.java @@ -79,17 +79,6 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { // then the Hibernate class loader orderedClassLoaderSet.add( ClassLoaderServiceImpl.class.getClassLoader() ); - // then the TCCL, if one... - final ClassLoader tccl = locateTCCL(); - if ( tccl != null ) { - orderedClassLoaderSet.add( tccl ); - } - // finally the system classloader - final ClassLoader sysClassLoader = locateSystemClassLoader(); - if ( sysClassLoader != null ) { - orderedClassLoaderSet.add( sysClassLoader ); - } - // now build the aggregated class loader... this.aggregatedClassLoader = new AggregatedClassLoader( orderedClassLoaderSet ); } @@ -120,15 +109,6 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { addIfSet( providedClassLoaders, AvailableSettings.HIBERNATE_CLASSLOADER, configValues ); addIfSet( providedClassLoaders, AvailableSettings.ENVIRONMENT_CLASSLOADER, configValues ); - if ( providedClassLoaders.isEmpty() ) { - log.debugf( "Incoming config yielded no classloaders; adding standard SE ones" ); - final ClassLoader tccl = locateTCCL(); - if ( tccl != null ) { - providedClassLoaders.add( tccl ); - } - providedClassLoaders.add( ClassLoaderServiceImpl.class.getClassLoader() ); - } - return new ClassLoaderServiceImpl( providedClassLoaders ); } @@ -157,19 +137,96 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { } } + @Override + public TCCLLookupBehavior getTTCLLookupBehavior() { + return getAggregatedClassLoader().tcclLookupBehavior; + } + + @Override + public void setTCCLLookupBehavior(TCCLLookupBehavior behavior) { + getAggregatedClassLoader().tcclLookupBehavior = behavior; + } + private static class AggregatedClassLoader extends ClassLoader { private final ClassLoader[] individualClassLoaders; + private volatile TCCLLookupBehavior tcclLookupBehavior = TCCLLookupBehavior.AFTER; private AggregatedClassLoader(final LinkedHashSet orderedClassLoaderSet) { super( null ); individualClassLoaders = orderedClassLoaderSet.toArray( new ClassLoader[orderedClassLoaderSet.size()] ); } + private Iterator newClassLoaderIterator() { + final ClassLoader sysClassLoader = locateSystemClassLoader(); + final ClassLoader threadClassLoader = locateTCCL(); + + final TCCLLookupBehavior behavior; + if ( threadClassLoader == null ) { + behavior = TCCLLookupBehavior.NEVER; + } + else { + behavior = tcclLookupBehavior; + } + + return new Iterator() { + private boolean tcclReturned = false; + private boolean sysclReturned = false; + private int currentIndex = 0; + + private ClassLoader nextClassLoader; + + @Override + public boolean hasNext() { + if ( nextClassLoader != null ) { + return true; + } + + if ( currentIndex == 0 && behavior == TCCLLookupBehavior.BEFORE && !tcclReturned ) { + tcclReturned = true; + nextClassLoader = threadClassLoader; + return true; + } + + if ( currentIndex < individualClassLoaders.length ) { + nextClassLoader = individualClassLoaders[ currentIndex ]; + ++currentIndex; + return true; + } + + if ( behavior == TCCLLookupBehavior.AFTER && !tcclReturned ) { + tcclReturned = true; + nextClassLoader = threadClassLoader; + return true; + } + + if ( !sysclReturned ) { + sysclReturned = true; + nextClassLoader = sysClassLoader; + return true; + } + + return false; + } + + @Override + public ClassLoader next() { + if ( nextClassLoader == null ) { + throw new IllegalStateException( "No more ClassLoader to return" ); + } + + ClassLoader result = nextClassLoader; + nextClassLoader = null; + return result; + } + }; + } + @Override public Enumeration getResources(String name) throws IOException { final LinkedHashSet resourceUrls = new LinkedHashSet(); - - for ( ClassLoader classLoader : individualClassLoaders ) { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); final Enumeration urls = classLoader.getResources( name ); while ( urls.hasMoreElements() ) { resourceUrls.add( urls.nextElement() ); @@ -193,7 +250,9 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { @Override protected URL findResource(String name) { - for ( ClassLoader classLoader : individualClassLoaders ) { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); final URL resource = classLoader.getResource( name ); if ( resource != null ) { return resource; @@ -204,7 +263,9 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { @Override protected Class findClass(String name) throws ClassNotFoundException { - for ( ClassLoader classLoader : individualClassLoaders ) { + final Iterator clIterator = newClassLoaderIterator(); + while ( clIterator.hasNext() ) { + final ClassLoader classLoader = clIterator.next(); try { return classLoader.loadClass( name ); } @@ -358,8 +419,8 @@ public class ClassLoaderServiceImpl implements ClassLoaderService { return work.doWork( getAggregatedClassLoader() ); } - private ClassLoader getAggregatedClassLoader() { - final ClassLoader aggregated = this.aggregatedClassLoader; + private AggregatedClassLoader getAggregatedClassLoader() { + final AggregatedClassLoader aggregated = this.aggregatedClassLoader; if ( aggregated == null ) { throw log.usingStoppedClassLoaderService(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/spi/ClassLoaderService.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/spi/ClassLoaderService.java index ba455e0108..e693db11f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/spi/ClassLoaderService.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/spi/ClassLoaderService.java @@ -80,4 +80,33 @@ public interface ClassLoaderService extends Service, Stoppable { } T workWithClassLoader(Work work); + + /** + * Defines of the lookup in the current thread context {@link ClassLoader} should be + * used. + */ + enum TCCLLookupBehavior { + + /** + * The current thread context {@link ClassLoader} will never be used during + * the class lookup. + */ + NEVER, + + /** + * The class lookup will be done in the thread context {@link ClassLoader} prior + * to the other {@code ClassLoader}s. + */ + BEFORE, + + /** + * The class lookup will be done in the thread context {@link ClassLoader} if + * the former hasn't been found in the other {@code ClassLoader}s. + * This is the default value. + */ + AFTER + } + + TCCLLookupBehavior getTTCLLookupBehavior(); + void setTCCLLookupBehavior(TCCLLookupBehavior behavior); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index b497499e06..9a37b36c87 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -7,6 +7,7 @@ package org.hibernate.cfg; import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; @@ -190,6 +191,14 @@ public interface AvailableSettings { */ String CLASSLOADERS = "hibernate.classLoaders"; + /** + * Used to define how the current thread context {@link ClassLoader} must be used + * for class lookup. + * + * @see ClassLoaderService#TCCLLookupBehavior + */ + String TC_CLASSLOADER = "hibernate.classloader.tccl_lookup"; + /** * Names the {@link ClassLoader} used to load user application classes. * @since 4.0 diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 1526594165..6aeae50c05 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.AttributeConverter; @@ -44,6 +45,7 @@ import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService.TCCLLookupBehavior; import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.boot.spi.MetadataBuilderImplementor; @@ -390,6 +392,15 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil bsrBuilder.applyClassLoader( (ClassLoader) classLoadersSetting ); } } + + //configurationValues not assigned yet, using directly the properties of the PU + Properties puProperties = persistenceUnit.getProperties(); + if( puProperties != null ) { + final String tcclBehavior = puProperties.getProperty( org.hibernate.cfg.AvailableSettings.TC_CLASSLOADER ); + if( tcclBehavior != null ) { + bsrBuilder.applyTCCLBehavior( TCCLLookupBehavior.valueOf( tcclBehavior.toUpperCase() ) ); + } + } } return bsrBuilder.build(); diff --git a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java new file mode 100644 index 0000000000..463affa90f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.boot.registry.classloading.internal; + +import java.net.URL; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * @author Cedric Tabin + */ +public class ClassLoaderServiceImplTest { + @Test + public void testNullTCCL() { + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + Thread.currentThread().setContextClassLoader(null); + + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); + Class clazz1 = csi.classForName(ClassLoaderServiceImplTest.class.getName()); + assertEquals(ClassLoaderServiceImplTest.class, clazz1); + + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER); + Class clazz2 = csi.classForName(ClassLoaderServiceImplTest.class.getName()); + assertEquals(ClassLoaderServiceImplTest.class, clazz2); + + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.NEVER); + Class clazz3 = csi.classForName(ClassLoaderServiceImplTest.class.getName()); + assertEquals(ClassLoaderServiceImplTest.class, clazz3); + + csi.stop(); + try { csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); assertTrue(false); } catch (Exception e) { } + try { csi.getTTCLLookupBehavior(); assertTrue(false); } catch (Exception e) { } + } + + @Test + public void testLookupBefore() { + InternalClassLoader icl = new InternalClassLoader(); + Thread.currentThread().setContextClassLoader(icl); + + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); + Class clazz = csi.classForName(ClassLoaderServiceImplTest.class.getName()); + assertEquals(ClassLoaderServiceImplTest.class, clazz); + assertEquals(1, icl.accessCount); + } + + @Test + public void testLookupAfterAvoided() { + InternalClassLoader icl = new InternalClassLoader(); + Thread.currentThread().setContextClassLoader(icl); + + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER); + Class clazz = csi.classForName(ClassLoaderServiceImplTest.class.getName()); + assertEquals(ClassLoaderServiceImplTest.class, clazz); + assertEquals(0, icl.accessCount); + } + + @Test + public void testLookupAfter() { + InternalClassLoader icl = new InternalClassLoader(); + Thread.currentThread().setContextClassLoader(icl); + + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER); + try { csi.classForName("test.class.name"); assertTrue(false); } + catch (Exception e) {} + assertEquals(1, icl.accessCount); + } + + @Test + public void testLookupAfterNotFound() { + InternalClassLoader icl = new InternalClassLoader(); + Thread.currentThread().setContextClassLoader(icl); + + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.AFTER); + try { csi.classForName("test.class.not.found"); assertTrue(false); } + catch (Exception e) { } + assertEquals(1, icl.accessCount); + } + + @Test + public void testLookupNever() { + InternalClassLoader icl = new InternalClassLoader(); + Thread.currentThread().setContextClassLoader(icl); + + ClassLoaderServiceImpl csi = new ClassLoaderServiceImpl(); + csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.NEVER); + try { csi.classForName("test.class.name"); assertTrue(false); } + catch (Exception e) { } + assertEquals(0, icl.accessCount); + + csi.stop(); + try { csi.setTCCLLookupBehavior(ClassLoaderService.TCCLLookupBehavior.BEFORE); assertTrue(false); } catch (Exception e) { } + try { csi.getTTCLLookupBehavior(); assertTrue(false); } catch (Exception e) { } + } + + private static class InternalClassLoader extends ClassLoader { + private int accessCount = 0; + + public InternalClassLoader() { super(null); } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + ++accessCount; + return super.loadClass(name); + } + + @Override + protected URL findResource(String name) { + ++accessCount; + return super.findResource(name); + } + } +}