HHH-12327 - Remove the Envers dependency on Javassist.

This commit is contained in:
Chris Cranford 2018-03-02 12:40:34 -05:00
parent 493c968141
commit 4a3f7c19c0
13 changed files with 208 additions and 392 deletions

View File

@ -12,8 +12,6 @@ description = 'Hibernate\'s entity version (audit/history) support'
dependencies {
compile( project( ':hibernate-core' ) )
//Ideally javassist should be only an optional dependency but it's currently required by Envers: see HHH-12327
compile( libraries.javassist )
provided( [group: 'org.hibernate', name: 'hibernate-tools', version: '3.2.0.ga'] )
provided( libraries.ant )
@ -21,7 +19,6 @@ dependencies {
testCompile( project( ':hibernate-testing' ) )
testCompile( project( path: ':hibernate-core', configuration: 'tests' ) )
testRuntime( libraries.byteBuddy )
}
sourceSets {

View File

@ -0,0 +1,26 @@
/*
* 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.envers.internal.entities.mapper;
/**
* Abstract implementation of a {@link PropertyMapper}.
*
* @author Chris Cranford
*/
public abstract class AbstractPropertyMapper implements PropertyMapper {
private boolean map;
@Override
public void markAsDynamicComponentMap() {
this.map = true;
}
@Override
public boolean isDynamicComponentMap() {
return map;
}
}

View File

@ -29,7 +29,7 @@ import org.hibernate.property.access.spi.Setter;
* @author Lukasz Zuchowski (author at zuchos dot com)
* @author Chris Cranford
*/
public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperBuilder {
public class ComponentPropertyMapper extends AbstractPropertyMapper implements CompositeMapperBuilder {
private final PropertyData propertyData;
private final MultiPropertyMapper delegate;
private final Class componentClass;
@ -122,6 +122,14 @@ public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperB
new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
final Object subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
if ( isDynamicComponentMap() ) {
( (Map) obj ).put( propertyData.getBeanName(), subObj );
delegate.mapToEntityFromMap( enversService, subObj, data, primaryKey, versionsReader, revision );
}
else {
final Setter setter = ReflectionTools.getSetter(
obj.getClass(),
propertyData,
@ -134,16 +142,14 @@ public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperB
}
else {
// set the component
try {
final Object subObj = ReflectHelper.getDefaultConstructor( componentClass ).newInstance();
setter.set( obj, subObj, null );
delegate.mapToEntityFromMap( enversService, subObj, data, primaryKey, versionsReader, revision );
}
}
}
catch ( Exception e ) {
throw new AuditException( e );
}
}
return null;
}

View File

@ -0,0 +1,28 @@
/*
* 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.envers.internal.entities.mapper;
/**
* Contract for {@link PropertyMapper} implementations to expose whether they should be included
* as a wrapper for a {@code <dynamic-component/>} mapping.
*
* In this mapping, values are actually stored as a key-value pair in a HashMap rather than
* them being treated as java-bean values using a setter method.
*
* @author Chris Cranford
*/
public interface DynamicComponentMapperSupport {
/**
* Mark the property mapper that it wraps a dynamic-component.
*/
void markAsDynamicComponentMap();
/**
* Returns whether the property mapper wraps a dynamic-component.
*/
boolean isDynamicComponentMap();
}

View File

@ -12,13 +12,12 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.MapProxyTool;
import org.hibernate.envers.internal.tools.StringTools;
/**
* Multi mapper for dynamic components (it knows that component is a map, not a class)
*
* @author Lukasz Zuchowski (author at zuchos dot com)
* @author Chris Cranford
*/
public class MultiDynamicComponentMapper extends MultiPropertyMapper {
@ -28,6 +27,22 @@ public class MultiDynamicComponentMapper extends MultiPropertyMapper {
this.dynamicComponentData = dynamicComponentData;
}
@Override
public void addComposite(PropertyData propertyData, PropertyMapper propertyMapper) {
// delegate to the super implementation and then mark the property mapper as a dynamic-component map.
super.addComposite( propertyData, propertyMapper );
propertyMapper.markAsDynamicComponentMap();
}
@Override
public void add(PropertyData propertyData) {
final SinglePropertyMapper single = new SinglePropertyMapper();
single.add( propertyData );
// delegate to our implementation of #addComposite for specialized behavior.
addComposite( propertyData, single );
}
@Override
public boolean mapToMapFromEntity(
SessionImplementor session,
@ -96,7 +111,6 @@ public class MultiDynamicComponentMapper extends MultiPropertyMapper {
}
@Override
@SuppressWarnings("unchecked")
public void mapToEntityFromMap(
EnversService enversService,
Object obj,
@ -104,22 +118,8 @@ public class MultiDynamicComponentMapper extends MultiPropertyMapper {
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
Object mapProxy = MapProxyTool.newInstanceOfBeanProxyForMap(
generateClassName( data, dynamicComponentData.getBeanName() ),
(Map) obj,
properties.keySet(),
enversService.getClassLoaderService()
);
for ( PropertyMapper mapper : properties.values() ) {
mapper.mapToEntityFromMap( enversService, mapProxy, data, primaryKey, versionsReader, revision );
mapper.mapToEntityFromMap( enversService, obj, data, primaryKey, versionsReader, revision );
}
}
private String generateClassName(Map data, String dynamicComponentPropertyName) {
return ( data.get( "$type$" ) + StringTools.capitalizeFirst( dynamicComponentPropertyName ) ).replaceAll(
"_",
""
);
}
}

View File

@ -29,7 +29,7 @@ import org.hibernate.property.access.spi.Getter;
* @author Lukasz Zuchowski (author at zuchos dot com)
* @author Chris Cranford
*/
public class MultiPropertyMapper implements ExtendedPropertyMapper {
public class MultiPropertyMapper extends AbstractPropertyMapper implements ExtendedPropertyMapper {
protected final Map<PropertyData, PropertyMapper> properties;
private final Map<String, PropertyData> propertyDatas;

View File

@ -20,7 +20,7 @@ import org.hibernate.envers.internal.reader.AuditReaderImplementor;
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public interface PropertyMapper extends ModifiedFlagMapperSupport {
public interface PropertyMapper extends ModifiedFlagMapperSupport, DynamicComponentMapperSupport {
/**
* Maps properties to the given map, basing on differences between properties of new and old objects.
*

View File

@ -33,7 +33,7 @@ import org.hibernate.property.access.spi.SetterFieldImpl;
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder {
public class SinglePropertyMapper extends AbstractPropertyMapper implements SimpleMapperBuilder {
private PropertyData propertyData;
public SinglePropertyMapper(PropertyData propertyData) {
@ -97,6 +97,14 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
return;
}
final Object value = data.get( propertyData.getName() );
if ( isDynamicComponentMap() ) {
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) obj;
map.put( propertyData.getBeanName(), value );
}
else {
AccessController.doPrivileged(
new PrivilegedAction<Object>() {
@Override
@ -107,8 +115,6 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
enversService.getServiceRegistry()
);
final Object value = data.get( propertyData.getName() );
// We only set a null value if the field is not primitive. Otherwise, we leave it intact.
if ( value != null || !isPrimitive( setter, propertyData, obj.getClass() ) ) {
setter.set( obj, value, null );
@ -119,6 +125,7 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
}
);
}
}
private boolean isPrimitive(Setter setter, PropertyData propertyData, Class<?> cls) {
if ( cls == null ) {

View File

@ -25,7 +25,7 @@ import org.hibernate.envers.internal.reader.AuditReaderImplementor;
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public class SubclassPropertyMapper implements ExtendedPropertyMapper {
public class SubclassPropertyMapper extends AbstractPropertyMapper implements ExtendedPropertyMapper {
private ExtendedPropertyMapper main;
private ExtendedPropertyMapper parentMapper;

View File

@ -28,8 +28,8 @@ import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.entities.mapper.AbstractPropertyMapper;
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor.Initializor;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.ReflectionTools;
@ -42,7 +42,7 @@ import org.hibernate.property.access.spi.Setter;
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public abstract class AbstractCollectionMapper<T> implements PropertyMapper {
public abstract class AbstractCollectionMapper<T> extends AbstractPropertyMapper {
protected final CommonCollectionMapperData commonCollectionMapperData;
protected final Class<? extends T> collectionClass;
protected final boolean ordinalInId;
@ -256,6 +256,28 @@ public abstract class AbstractCollectionMapper<T> implements PropertyMapper {
final AuditReaderImplementor versionsReader,
final Number revision) {
final String revisionTypePropertyName = enversService.getAuditEntitiesConfiguration().getRevisionTypePropName();
if ( isDynamicComponentMap() ) {
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) obj;
try {
map.put(
commonCollectionMapperData.getCollectionReferencingPropertyData().getBeanName(),
proxyConstructor.newInstance(
getInitializor(
enversService,
versionsReader,
primaryKey,
revision,
RevisionType.DEL.equals( data.get( revisionTypePropertyName ) )
)
)
);
}
catch ( Exception e ) {
throw new AuditException( e );
}
}
else {
AccessController.doPrivileged(
new PrivilegedAction<Object>() {
@Override
@ -296,6 +318,7 @@ public abstract class AbstractCollectionMapper<T> implements PropertyMapper {
}
);
}
}
/**
* Map collection changes using hash identity.

View File

@ -17,8 +17,8 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityConfiguration;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.entities.mapper.AbstractPropertyMapper;
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.property.access.spi.Setter;
@ -30,7 +30,7 @@ import org.hibernate.service.ServiceRegistry;
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public abstract class AbstractToOneMapper implements PropertyMapper {
public abstract class AbstractToOneMapper extends AbstractPropertyMapper {
private final ServiceRegistry serviceRegistry;
private final PropertyData propertyData;
@ -90,6 +90,12 @@ public abstract class AbstractToOneMapper implements PropertyMapper {
}
protected void setPropertyValue(Object targetObject, Object value) {
if ( isDynamicComponentMap() ) {
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>) targetObject;
map.put( propertyData.getBeanName(), value );
}
else {
AccessController.doPrivileged(
new PrivilegedAction<Object>() {
@Override
@ -106,6 +112,7 @@ public abstract class AbstractToOneMapper implements PropertyMapper {
}
);
}
}
/**
* @return Bean property that represents the relation.

View File

@ -1,188 +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.envers.internal.tools;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.NotFoundException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.envers.internal.entities.PropertyData;
import static org.hibernate.envers.internal.tools.StringTools.capitalizeFirst;
import static org.hibernate.envers.internal.tools.StringTools.getLastComponent;
/**
* @author Lukasz Zuchowski (author at zuchos dot com)
*/
public final class MapProxyTool {
private MapProxyTool() {
}
/**
* Creates instance of map proxy class. This proxy class will be a java bean with properties from <code>propertyDatas</code>.
* Instance will proxy calls to instance of the map passed as parameter.
*
* @param className Name of the class to construct (should be unique within class loader)
* @param map instance that will be proxied by java bean
* @param propertyDatas properties that should java bean declare
* @param classLoaderService class loader service
*
* @return new instance of proxy
*/
public static Object newInstanceOfBeanProxyForMap(
String className,
Map<String, Object> map,
Set<PropertyData> propertyDatas,
ClassLoaderService classLoaderService) {
Map<String, Class<?>> properties = prepareProperties( propertyDatas );
return createNewInstance( map, classForName( className, properties, classLoaderService ) );
}
private static Object createNewInstance(Map<String, Object> map, Class aClass) {
try {
return aClass.getConstructor( Map.class ).newInstance( map );
}
catch (Exception e) {
throw new RuntimeException( e );
}
}
private static Map<String, Class<?>> prepareProperties(Set<PropertyData> propertyDatas) {
Map<String, Class<?>> properties = new HashMap<>();
for ( PropertyData propertyData : propertyDatas ) {
properties.put( propertyData.getBeanName(), Object.class );
}
return properties;
}
private static Class loadClass(String className, ClassLoaderService classLoaderService) {
try {
return ReflectionTools.loadClass( className, classLoaderService );
}
catch (ClassLoadingException e) {
return null;
}
}
/**
* Generates/loads proxy class for given name with properties for map.
*
* @param className name of the class that will be generated/loaded
* @param properties list of properties that should be exposed via java bean
* @param classLoaderService class loader service
*
* @return proxy class that wraps map into java bean
*/
public static Class classForName(
String className,
Map<String, Class<?>> properties,
ClassLoaderService classLoaderService) {
Class aClass = loadClass( className, classLoaderService );
if ( aClass == null ) {
aClass = generate( className, properties );
}
return aClass;
}
/**
* Protected for test only
*/
protected static Class generate(String className, Map<String, Class<?>> properties) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass( className );
cc.addInterface( resolveCtClass( Serializable.class ) );
cc.addField( new CtField( resolveCtClass( Map.class ), "theMap", cc ) );
cc.addConstructor( generateConstructor( className, cc ) );
for ( Entry<String, Class<?>> entry : properties.entrySet() ) {
// add getter
cc.addMethod( generateGetter( cc, entry.getKey(), entry.getValue() ) );
// add setter
cc.addMethod( generateSetter( cc, entry.getKey(), entry.getValue() ) );
}
return cc.toClass();
}
catch (Exception e) {
throw new RuntimeException( e );
}
}
private static CtConstructor generateConstructor(String className, CtClass cc)
throws NotFoundException, CannotCompileException {
StringBuffer sb = new StringBuffer();
sb.append( "public " )
.append( getLastComponent( className ) )
.append( "(" )
.append( Map.class.getName() )
.append( " map)" )
.append( "{" )
.append( "this.theMap = map;" )
.append( "}" );
return CtNewConstructor.make( sb.toString(), cc );
}
private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
throws CannotCompileException {
String getterName = "get" + capitalizeFirst( fieldName );
StringBuilder sb = new StringBuilder();
sb.append( "public " ).append( fieldClass.getName() ).append( " " )
.append( getterName ).append( "(){" ).append( "return (" ).append( fieldClass.getName() ).append(
")this.theMap.get(\""
)
.append( fieldName ).append( "\")" ).append( ";" ).append( "}" );
return CtMethod.make( sb.toString(), declaringClass );
}
private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass)
throws CannotCompileException {
String setterName = "set" + capitalizeFirst( fieldName );
StringBuilder sb = new StringBuilder();
sb.append( "public void " ).append( setterName ).append( "(" )
.append( fieldClass.getName() ).append( " " ).append( fieldName )
.append( ")" ).append( "{" ).append( "this.theMap.put(\"" ).append( fieldName )
.append( "\"," ).append( fieldName ).append( ")" ).append( ";" ).append( "}" );
return CtMethod.make( sb.toString(), declaringClass );
}
private static CtClass resolveCtClass(Class clazz) throws NotFoundException {
return resolveCtClass( clazz.getName() );
}
private static CtClass resolveCtClass(String clazz) throws NotFoundException {
try {
ClassPool pool = ClassPool.getDefault();
return pool.get( clazz );
}
catch (NotFoundException e) {
return null;
}
}
}

View File

@ -1,90 +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.envers.internal.tools;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.Setter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import junit.framework.Assert;
public class MapProxyTest {
private StandardServiceRegistry serviceRegistry;
@Before
public void prepare() {
serviceRegistry = new StandardServiceRegistryBuilder().build();
}
@After
public void release() {
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
@Test
public void shouldGenerateClassWithAppropriateGetter() throws Exception {
//given
Map<String, Object> map = new HashMap<String, Object>();
int ageExpected = 14;
map.put("age", ageExpected);
Map<String, Class<?>> properties = new HashMap<String, Class<?>>();
properties.put("age", Integer.class);
//when
Class testClass = MapProxyTool.classForName("TestClass1", properties, new ClassLoaderServiceImpl());
Object testClassInstance = testClass.getConstructor(Map.class).newInstance(map);
//then
Getter getter = ReflectionTools.getGetter( testClass, "age", "property", serviceRegistry );
int age = (Integer) getter.get(testClassInstance);
Assert.assertEquals(ageExpected, age);
}
@Test
public void shouldGenerateClassWithAppropriateSetter() throws Exception {
//given
Map<String, Object> map = new HashMap<String, Object>();
Map<String, Class<?>> properties = new HashMap<String, Class<?>>();
properties.put("age", Integer.class);
//when
Class testClass = MapProxyTool.classForName("TestClass2", properties, new ClassLoaderServiceImpl());
Object testClassInstance = testClass.getConstructor(Map.class).newInstance(map);
//then
Setter setter = ReflectionTools.getSetter(testClass, "age", "property", serviceRegistry);
int ageExpected = 14;
setter.set(testClassInstance, ageExpected, null);
Object age = map.get("age");
Assert.assertEquals(ageExpected, age);
}
@Test
public void shouldGenerateClassWithAppropriateAccessorsForBoolean() throws Exception {
//given
Map<String, Object> map = new HashMap<String, Object>();
map.put("checkbox",true);
Map<String, Class<?>> properties = new HashMap<String, Class<?>>();
properties.put("checkbox", Boolean.class);
//when
Class testClass = MapProxyTool.classForName("TestClass3", properties, new ClassLoaderServiceImpl());
Object testClassInstance = testClass.getConstructor(Map.class).newInstance(map);
//then
Getter getter = ReflectionTools.getGetter(testClass, "checkbox", "property", serviceRegistry);
Assert.assertTrue((Boolean) getter.get(testClassInstance));
}
}