HHH-10356 - Make runtime bytecode enhancement configurable

This commit is contained in:
barreiro 2015-12-04 18:11:34 +00:00 committed by Steve Ebersole
parent 668b07222c
commit 809e3cf4b9
12 changed files with 232 additions and 73 deletions

View File

@ -419,9 +419,27 @@ public interface AvailableSettings {
/**
* Enable the class file enhancement
*
* @deprecated Split into multiple 'hibernate.enhancer.enable[]' properties
*/
@Deprecated
String USE_CLASS_ENHANCER = "hibernate.ejb.use_class_enhancer";
/**
* Enable dirty tracking feature in runtime bytecode enhancement
*/
String ENHANCER_ENABLE_DIRTY_TRACKING = "hibernate.enhancer.enableDirtyTracking";
/**
* Enable lazy loading feature in runtime bytecode enhancement
*/
String ENHANCER_ENABLE_LAZY_INITIALIZATION = "hibernate.enhancer.enableLazyInitialization";
/**
* Enable association management feature in runtime bytecode enhancement
*/
String ENHANCER_ENABLE_ASSOCIATION_MANAGEMENT = "hibernate.enhancer.enableAssociationManagement";
/**
* Whether or not discard persistent context on entityManager.close()
* The EJB3 compliant and default choice is false

View File

@ -22,6 +22,9 @@ import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import javassist.CtClass;
import javassist.CtField;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
@ -45,6 +48,8 @@ import org.hibernate.boot.registry.selector.StrategyRegistrationProvider;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.MetadataBuilderImplementor;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.cfg.Environment;
import org.hibernate.cfg.beanvalidation.BeanValidationIntegrator;
@ -201,9 +206,25 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// push back class transformation to the environment; for the time being this only has any effect in EE
// container situations, calling back into PersistenceUnitInfo#addClassTransformer
final boolean useClassTransformer = "true".equals( configurationValues.remove( AvailableSettings.USE_CLASS_ENHANCER ) );
if ( useClassTransformer ) {
persistenceUnit.pushClassTransformer( managedResources.getAnnotatedClassNames() );
final boolean dirtyTrackingEnabled = readBooleanConfigurationValue( AvailableSettings.ENHANCER_ENABLE_DIRTY_TRACKING );
final boolean lazyInitializationEnabled = readBooleanConfigurationValue( AvailableSettings.ENHANCER_ENABLE_LAZY_INITIALIZATION );
final boolean associationManagementEnabled = readBooleanConfigurationValue( AvailableSettings.ENHANCER_ENABLE_ASSOCIATION_MANAGEMENT );
// todo: remove the support for the old instrumentation property in the future
final boolean deprecatedInstrumentation = readBooleanConfigurationValue( AvailableSettings.USE_CLASS_ENHANCER );
if ( deprecatedInstrumentation ) {
LOG.deprecatedInstrumentationProperty();
}
if ( deprecatedInstrumentation || dirtyTrackingEnabled || lazyInitializationEnabled || associationManagementEnabled ) {
EnhancementContext enhancementContext = getEnhancementContext(
deprecatedInstrumentation || dirtyTrackingEnabled,
deprecatedInstrumentation || lazyInitializationEnabled,
deprecatedInstrumentation || associationManagementEnabled
);
persistenceUnit.pushClassTransformer( enhancementContext );
}
// for the time being we want to revoke access to the temp ClassLoader if one was passed
@ -218,6 +239,62 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private boolean readBooleanConfigurationValue(String propertyName) {
Object propertyValue = configurationValues.remove( propertyName );
return propertyValue != null && Boolean.parseBoolean( propertyValue.toString() );
}
/**
* Builds the context to be used in runtime bytecode enhancement
*
* @param dirtyTrackingEnabled To enable dirty tracking feature
* @param lazyInitializationEnabled To enable lazy initialization feature
* @param associationManagementEnabled To enable association management feature
* @return An enhancement context for classes managed by this EM
*/
protected EnhancementContext getEnhancementContext(final boolean dirtyTrackingEnabled, final boolean lazyInitializationEnabled, final boolean associationManagementEnabled ) {
return new DefaultEnhancementContext() {
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return managedResources.getAnnotatedClassNames().contains( classDescriptor.getName() )
&& super.isEntityClass( classDescriptor );
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return managedResources.getAnnotatedClassNames().contains( classDescriptor.getName() )
&& super.isCompositeClass( classDescriptor );
}
@Override
public boolean doBiDirectionalAssociationManagement(CtField field) {
return associationManagementEnabled;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return dirtyTrackingEnabled;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return lazyInitializationEnabled;
}
@Override
public boolean isLazyLoadable(CtField field) {
return lazyInitializationEnabled;
}
@Override
public boolean doExtendedEnhancement(CtClass classDescriptor) {
// doesn't make any sense to have extended enhancement enabled at runtime. we only enhance entities anyway.
return false;
}
};
}
/**
* Builds the {@link BootstrapServiceRegistry} used to eventually build the {@link org.hibernate.boot.registry.StandardServiceRegistryBuilder}; mainly

View File

@ -9,13 +9,14 @@ package org.hibernate.jpa.boot.internal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
/**
* Describes the information gleaned from a {@code <persistence-unit/>} element in a {@code persistence.xml} file
* whether parsed directly by Hibernate or passed to us by an EE container as a
@ -185,7 +186,7 @@ public class ParsedPersistenceXmlDescriptor implements org.hibernate.jpa.boot.sp
}
@Override
public void pushClassTransformer(Collection<String> entityClassNames) {
public void pushClassTransformer(EnhancementContext enhancementContext) {
// todo : log a message that this is currently not supported...
}
}

View File

@ -7,7 +7,6 @@
package org.hibernate.jpa.boot.internal;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import javax.persistence.SharedCacheMode;
@ -15,6 +14,7 @@ import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
import org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl;
@ -109,7 +109,7 @@ public class PersistenceUnitInfoDescriptor implements PersistenceUnitDescriptor
}
@Override
public void pushClassTransformer(Collection<String> entityClassNames) {
persistenceUnitInfo.addTransformer( new EnhancingClassTransformerImpl( entityClassNames ) );
public void pushClassTransformer(EnhancementContext enhancementContext) {
persistenceUnitInfo.addTransformer( new EnhancingClassTransformerImpl( enhancementContext ) );
}
}

View File

@ -7,13 +7,14 @@
package org.hibernate.jpa.boot.spi;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
/**
* Abstraction for dealing with either {@code <persistence-unit/>} information whether that comes from
* an EE container in the form of {@link javax.persistence.spi.PersistenceUnitInfo} or in an SE environment
@ -80,7 +81,8 @@ public interface PersistenceUnitDescriptor {
public Properties getProperties();
public ClassLoader getClassLoader();
public ClassLoader getTempClassLoader();
public void pushClassTransformer(Collection<String> entityClassNames);
public void pushClassTransformer(EnhancementContext enhancementContext);
}

View File

@ -119,4 +119,13 @@ public interface EntityManagerMessageLogger extends CoreMessageLogger {
value = "Encountered a deprecated javax.persistence.spi.PersistenceProvider [%s]; use [%s] instead."
)
void deprecatedPersistenceProvider(String deprecated, String replacement);
@LogMessage(level = WARN)
@Message(
id = 15017,
value = "'hibernate.ejb.use_class_enhancer' property is deprecated. " +
"Use 'hibernate.enhance.enable[...]' properties instead to enable each individual feature."
)
void deprecatedInstrumentationProperty();
}

View File

@ -1,43 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.internal.enhance;
import java.util.Collection;
import javassist.CtClass;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
/**
* @author Steve Ebersole
*/
public class EnhancementContextImpl extends DefaultEnhancementContext {
private final Collection<String> classNames;
private final ClassLoader classLoader;
public EnhancementContextImpl(Collection<String> classNames, ClassLoader classLoader) {
this.classNames = classNames;
this.classLoader = classLoader;
}
@Override
public ClassLoader getLoadingClassLoader() {
return classLoader;
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return classNames.contains( classDescriptor.getName() )
&& super.isEntityClass( classDescriptor );
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return classNames.contains( classDescriptor.getName() )
&& super.isCompositeClass( classDescriptor );
}
}

View File

@ -12,19 +12,22 @@ import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.spi.ClassTransformer;
import javassist.CtClass;
import javassist.CtField;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
/**
* @author Steve Ebersole
* @author Luis Barreiro
*/
public class EnhancingClassTransformerImpl implements ClassTransformer {
private final Collection<String> classNames;
private Enhancer enhancer;
private final EnhancementContext enhancementContext;
public EnhancingClassTransformerImpl(Collection<String> incomingClassNames) {
this.classNames = new ArrayList<String>( incomingClassNames.size() );
this.classNames.addAll( incomingClassNames );
public EnhancingClassTransformerImpl(EnhancementContext enhancementContext) {
this.enhancementContext = enhancementContext;
}
@Override
@ -34,15 +37,17 @@ public class EnhancingClassTransformerImpl implements ClassTransformer {
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if ( enhancer == null ) {
enhancer = new Enhancer( new EnhancementContextImpl( classNames, loader ) );
}
// The first design had the enhancer as a class variable. That approach had some goods and bads.
// We don't had to create an enhancer for each class, but on the other end it would stay in memory forever.
// It also assumed that all calls come from the same class loader, which is fair, but this makes it more robust.
try {
Enhancer enhancer = new Enhancer( new EnhancementContextWrapper( enhancementContext, loader ) );
return enhancer.enhance( className, classfileBuffer );
}
catch (final Exception e) {
throw new IllegalClassFormatException( "Error performing enhancement" ) {
throw new IllegalClassFormatException( "Error performing enhancement of " + className ) {
@Override
public synchronized Throwable getCause() {
return e;
@ -51,4 +56,71 @@ public class EnhancingClassTransformerImpl implements ClassTransformer {
}
}
// Wrapper for a EnhancementContext that allows to set the right classloader.
private class EnhancementContextWrapper implements EnhancementContext {
private final ClassLoader loadingClassloader;
private final EnhancementContext wrappedContext;
private EnhancementContextWrapper(EnhancementContext wrappedContext, ClassLoader loadingClassloader) {
this.wrappedContext = wrappedContext;
this.loadingClassloader = loadingClassloader;
}
@Override
public ClassLoader getLoadingClassLoader() {
return loadingClassloader;
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return wrappedContext.isEntityClass( classDescriptor );
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return wrappedContext.isCompositeClass( classDescriptor );
}
@Override
public boolean doBiDirectionalAssociationManagement(CtField field) {
return wrappedContext.doBiDirectionalAssociationManagement( field );
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return wrappedContext.doDirtyCheckingInline( classDescriptor );
}
@Override
public boolean doExtendedEnhancement(CtClass classDescriptor) {
return wrappedContext.doExtendedEnhancement( classDescriptor );
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return wrappedContext.hasLazyLoadableAttributes( classDescriptor );
}
@Override
public boolean isPersistentField(CtField ctField) {
return wrappedContext.isPersistentField( ctField );
}
@Override
public CtField[] order(CtField[] persistentFields) {
return wrappedContext.order( persistentFields );
}
@Override
public boolean isLazyLoadable(CtField field) {
return wrappedContext.isLazyLoadable( field );
}
@Override
public boolean isMappedCollection(CtField field) {
return wrappedContext.isMappedCollection( field );
}
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.jpa.test;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -21,6 +20,7 @@ import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper;
@ -177,7 +177,7 @@ public abstract class BaseEntityManagerFunctionalTestCase extends BaseUnitTestCa
}
@Override
public void pushClassTransformer(Collection<String> entityClassNames) {
public void pushClassTransformer(EnhancementContext enhancementContext) {
}
}

View File

@ -7,7 +7,6 @@
package org.hibernate.jpa.test;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
@ -16,6 +15,7 @@ import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
@ -110,6 +110,6 @@ public class PersistenceUnitDescriptorAdapter implements PersistenceUnitDescript
}
@Override
public void pushClassTransformer(Collection<String> entityClassNames) {
public void pushClassTransformer(EnhancementContext enhancementContext) {
}
}

View File

@ -13,6 +13,10 @@ import java.io.InputStream;
import java.lang.instrument.IllegalClassFormatException;
import java.util.List;
import javassist.CtClass;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl;
/**
@ -79,10 +83,10 @@ public class InstrumentedClassLoader extends ClassLoader {
catch (IOException e) {
throw new ClassNotFoundException( name + " not found", e );
}
EnhancingClassTransformerImpl t = new EnhancingClassTransformerImpl( entities );
byte[] transformed = new byte[0];
EnhancingClassTransformerImpl t = new EnhancingClassTransformerImpl( getEnhancementContext( getParent(), entities ) );
try {
transformed = t.transform(
return t.transform(
getParent(),
name,
null,
@ -93,11 +97,30 @@ public class InstrumentedClassLoader extends ClassLoader {
catch (IllegalClassFormatException e) {
throw new ClassNotFoundException( name + " not found", e );
}
return transformed;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
public EnhancementContext getEnhancementContext(final ClassLoader cl, final List<String> entities) {
return new DefaultEnhancementContext() {
@Override
public ClassLoader getLoadingClassLoader() {
return cl;
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return entities.contains( classDescriptor.getName() ) && super.isEntityClass( classDescriptor );
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return entities.contains( classDescriptor.getName() ) && super.isCompositeClass( classDescriptor );
}
};
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.jpa.test.instrument.cases;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@ -18,6 +17,7 @@ import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.spi.InstrumentedClassLoader;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
@ -174,7 +174,7 @@ public abstract class AbstractExecutable implements Executable {
}
@Override
public void pushClassTransformer(Collection<String> entityClassNames) {
public void pushClassTransformer(EnhancementContext enhancementContext) {
}
};
}