/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.test.internal.metadata; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotSame; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.hibernate.validator.internal.engine.DefaultParameterNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.cascading.ValueExtractorManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Hardy Ferentschik */ public class BeanMetaDataManagerTest { private static final Log log = LoggerFactory.make(); private static final int LOOP_COUNT = 100000; private static final int ARRAY_ALLOCATION_SIZE = 100000; private BeanMetaDataManager metaDataManager; @BeforeMethod public void setUpBeanMetaDataManager() { metaDataManager = new BeanMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), Collections.<MetaDataProvider>emptyList(), new MethodValidationConfiguration.Builder().build() ); } @Test(enabled = false, description = "Disabled as it shows false failures too often. Run on demand if required") public void testBeanMetaDataCanBeGarbageCollected() throws Exception { Class<?> lastIterationsBean = null; int totalCreatedMetaDataInstances = 0; int cachedBeanMetaDataInstances = 0; try { // help along the OutOfMemoryError by allocating extra memory and holding on to it in this list List<Object> memoryConsumer = new ArrayList<>(); for ( int i = 0; i < LOOP_COUNT; i++ ) { Class<?> c = new CustomClassLoader().loadClass( Engine.class.getName() ); BeanMetaData<?> meta = metaDataManager.getBeanMetaData( c ); assertNotSame( meta.getBeanClass(), lastIterationsBean, "The classes should differ in each iteration" ); lastIterationsBean = meta.getBeanClass(); totalCreatedMetaDataInstances++; cachedBeanMetaDataInstances = metaDataManager.numberOfCachedBeanMetaDataInstances(); if ( cachedBeanMetaDataInstances < totalCreatedMetaDataInstances ) { log.debug( "Garbage collection occurred and some metadata instances got garbage collected!" ); log.debug( "totalCreatedMetaDataInstances:" + totalCreatedMetaDataInstances ); log.debug( "cachedBeanMetaDataInstances:" + cachedBeanMetaDataInstances ); break; } memoryConsumer.add( new long[ARRAY_ALLOCATION_SIZE] ); } } catch (OutOfMemoryError e) { log.debug( "Out of memory error occurred." ); log.debug( "totalCreatedMetaDataInstances:" + totalCreatedMetaDataInstances ); log.debug( "cachedBeanMetaDataInstances:" + cachedBeanMetaDataInstances ); } // Before an OutOfMemoryError occurs soft references should be collected. If not all, at least some // of the cached instances should have been released. if ( !( cachedBeanMetaDataInstances < totalCreatedMetaDataInstances ) ) { fail( "Metadata instances should be garbage collectible" ); } } @Test public void testGetMetaDataForConstrainedEntity() { BeanMetaData<?> beanMetaData = metaDataManager.getBeanMetaData( Engine.class ); assertTrue( beanMetaData instanceof BeanMetaDataImpl ); assertTrue( beanMetaData.hasConstraints() ); } @Test public void testGetMetaDataForUnConstrainedEntity() { BeanMetaData<?> beanMetaData = metaDataManager.getBeanMetaData( UnconstrainedEntity.class ); assertTrue( beanMetaData instanceof BeanMetaDataImpl, "#getBeanMetaData should always return a valid BeanMetaData instance. Returned class: " + beanMetaData.getClass() ); assertFalse( beanMetaData.hasConstraints() ); } public class CustomClassLoader extends ClassLoader { /** * Classes from this name space will be loaded by this class loader, all * others will be loaded by the default loader. */ private static final String PACKAGE_PREFIX = "org.hibernate.validator.test"; public CustomClassLoader() { super( CustomClassLoader.class.getClassLoader() ); } @Override public Class<?> loadClass(String className) throws ClassNotFoundException { if ( className.startsWith( PACKAGE_PREFIX ) ) { return myLoadClass( className, true ); } else { return super.loadClass( className ); } } protected Class<?> myLoadClass(String name, boolean resolve) throws ClassNotFoundException { // make sure there is no parent delegation, instead call custom findClass Class<?> c = myFindClass( name ); if ( resolve ) { resolveClass( c ); } return c; } public Class<?> myFindClass(String className) { byte[] classByte; Class<?> result; try { String classPath = ClassLoader.getSystemResource( className.replace( '.', '/' ) + ".class" ).getFile(); classByte = loadClassData( classPath ); result = defineClass( className, classByte, 0, classByte.length, null ); return result; } catch (Exception e) { return null; } } private byte[] loadClassData(String className) throws IOException { File f; f = new File( className ); int size = (int) f.length(); byte[] buff = new byte[size]; FileInputStream fis = new FileInputStream( f ); DataInputStream dis = new DataInputStream( fis ); dis.readFully( buff ); dis.close(); return buff; } } public static class UnconstrainedEntity { @SuppressWarnings("unused") private String foo; } }