HHH-8505: Auditing of dynamic components
This commit is contained in:
parent
fcaf413acc
commit
f70189f416
|
@ -23,10 +23,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.configuration.internal.metadata;
|
package org.hibernate.envers.configuration.internal.metadata;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
|
|
||||||
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
|
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
|
||||||
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
|
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
|
||||||
import org.hibernate.envers.internal.entities.mapper.CompositeMapperBuilder;
|
import org.hibernate.envers.internal.entities.mapper.CompositeMapperBuilder;
|
||||||
|
@ -35,52 +32,64 @@ import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates metadata for components.
|
* Generates metadata for components.
|
||||||
*
|
*
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
public final class ComponentMetadataGenerator {
|
public final class ComponentMetadataGenerator {
|
||||||
private final AuditMetadataGenerator mainGenerator;
|
private final AuditMetadataGenerator mainGenerator;
|
||||||
|
|
||||||
ComponentMetadataGenerator(AuditMetadataGenerator auditMetadataGenerator) {
|
ComponentMetadataGenerator(AuditMetadataGenerator auditMetadataGenerator) {
|
||||||
mainGenerator = auditMetadataGenerator;
|
mainGenerator = auditMetadataGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
public void addComponent(
|
public void addComponent(
|
||||||
Element parent, PropertyAuditingData propertyAuditingData,
|
Element parent, PropertyAuditingData propertyAuditingData,
|
||||||
Value value, CompositeMapperBuilder mapper, String entityName,
|
Value value, CompositeMapperBuilder mapper, String entityName,
|
||||||
EntityXmlMappingData xmlMappingData, boolean firstPass) {
|
EntityXmlMappingData xmlMappingData, boolean firstPass) {
|
||||||
final Component propComponent = (Component) value;
|
final Component propComponent = (Component) value;
|
||||||
|
|
||||||
final Class componentClass = ReflectionTools.loadClass(
|
final Class componentClass;
|
||||||
propComponent.getComponentClassName(),
|
if (propComponent.isDynamic()) {
|
||||||
mainGenerator.getClassLoaderService()
|
componentClass = ReflectionTools.loadClass(
|
||||||
);
|
Map.class.getCanonicalName(),
|
||||||
final CompositeMapperBuilder componentMapper = mapper.addComponent(
|
mainGenerator.getClassLoaderService());
|
||||||
propertyAuditingData.getPropertyData(),
|
|
||||||
componentClass
|
|
||||||
);
|
|
||||||
|
|
||||||
// The property auditing data must be for a component.
|
} else {
|
||||||
final ComponentAuditingData componentAuditingData = (ComponentAuditingData) propertyAuditingData;
|
componentClass = ReflectionTools.loadClass(
|
||||||
|
propComponent.getComponentClassName(),
|
||||||
|
mainGenerator.getClassLoaderService()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final CompositeMapperBuilder componentMapper = mapper.addComponent(
|
||||||
|
propertyAuditingData.getPropertyData(),
|
||||||
|
componentClass
|
||||||
|
);
|
||||||
|
|
||||||
// Adding all properties of the component
|
// The property auditing data must be for a component.
|
||||||
final Iterator<Property> properties = (Iterator<Property>) propComponent.getPropertyIterator();
|
final ComponentAuditingData componentAuditingData = (ComponentAuditingData) propertyAuditingData;
|
||||||
while ( properties.hasNext() ) {
|
|
||||||
final Property property = properties.next();
|
|
||||||
|
|
||||||
final PropertyAuditingData componentPropertyAuditingData =
|
// Adding all properties of the component
|
||||||
componentAuditingData.getPropertyAuditingData( property.getName() );
|
final Iterator<Property> properties = (Iterator<Property>) propComponent.getPropertyIterator();
|
||||||
|
while (properties.hasNext()) {
|
||||||
|
final Property property = properties.next();
|
||||||
|
|
||||||
// Checking if that property is audited
|
final PropertyAuditingData componentPropertyAuditingData =
|
||||||
if ( componentPropertyAuditingData != null ) {
|
componentAuditingData.getPropertyAuditingData(property.getName());
|
||||||
mainGenerator.addValue(
|
|
||||||
parent, property.getValue(), componentMapper, entityName, xmlMappingData,
|
// Checking if that property is audited
|
||||||
componentPropertyAuditingData, property.isInsertable(), firstPass, false
|
if (componentPropertyAuditingData != null) {
|
||||||
);
|
mainGenerator.addValue(
|
||||||
}
|
parent, property.getValue(), componentMapper, entityName, xmlMappingData,
|
||||||
}
|
componentPropertyAuditingData, property.isInsertable(), firstPass, false
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,30 +23,12 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.configuration.internal.metadata.reader;
|
package org.hibernate.envers.configuration.internal.metadata.reader;
|
||||||
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.MapKey;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import javax.persistence.Version;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.annotations.common.reflection.ReflectionManager;
|
import org.hibernate.annotations.common.reflection.ReflectionManager;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
import org.hibernate.envers.AuditJoinTable;
|
import org.hibernate.cfg.AccessType;
|
||||||
import org.hibernate.envers.AuditMappedBy;
|
import org.hibernate.envers.*;
|
||||||
import org.hibernate.envers.AuditOverride;
|
|
||||||
import org.hibernate.envers.AuditOverrides;
|
|
||||||
import org.hibernate.envers.Audited;
|
|
||||||
import org.hibernate.envers.ModificationStore;
|
|
||||||
import org.hibernate.envers.NotAudited;
|
|
||||||
import org.hibernate.envers.RelationTargetAuditMode;
|
|
||||||
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
||||||
import org.hibernate.envers.configuration.internal.metadata.MetadataTools;
|
import org.hibernate.envers.configuration.internal.metadata.MetadataTools;
|
||||||
import org.hibernate.envers.internal.tools.MappingTools;
|
import org.hibernate.envers.internal.tools.MappingTools;
|
||||||
|
@ -56,6 +38,13 @@ import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
|
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.MapKey;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Version;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import static org.hibernate.envers.internal.tools.Tools.newHashMap;
|
import static org.hibernate.envers.internal.tools.Tools.newHashMap;
|
||||||
import static org.hibernate.envers.internal.tools.Tools.newHashSet;
|
import static org.hibernate.envers.internal.tools.Tools.newHashSet;
|
||||||
|
|
||||||
|
@ -68,6 +57,7 @@ import static org.hibernate.envers.internal.tools.Tools.newHashSet;
|
||||||
* @author Hern&aacut;n Chanfreau
|
* @author Hern&aacut;n Chanfreau
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
* @author Michal Skowronek (mskowr at o2 dot pl)
|
* @author Michal Skowronek (mskowr at o2 dot pl)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
public class AuditedPropertiesReader {
|
public class AuditedPropertiesReader {
|
||||||
protected final ModificationStore defaultStore;
|
protected final ModificationStore defaultStore;
|
||||||
|
@ -117,14 +107,18 @@ public class AuditedPropertiesReader {
|
||||||
// First reading the access types for the persistent properties.
|
// First reading the access types for the persistent properties.
|
||||||
readPersistentPropertiesAccess();
|
readPersistentPropertiesAccess();
|
||||||
|
|
||||||
// Retrieve classes and properties that are explicitly marked for auditing process by any superclass
|
if (persistentPropertiesSource instanceof DynamicComponentSource) {
|
||||||
// of currently mapped entity or itself.
|
addPropertiesFromDynamicComponent((DynamicComponentSource) persistentPropertiesSource);
|
||||||
final XClass clazz = persistentPropertiesSource.getXClass();
|
} else {
|
||||||
readAuditOverrides( clazz );
|
// Retrieve classes and properties that are explicitly marked for auditing process by any superclass
|
||||||
|
// of currently mapped entity or itself.
|
||||||
|
final XClass clazz = persistentPropertiesSource.getXClass();
|
||||||
|
readAuditOverrides( clazz );
|
||||||
|
|
||||||
// Adding all properties from the given class.
|
// Adding all properties from the given class.
|
||||||
addPropertiesFromClass( clazz );
|
addPropertiesFromClass( clazz );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively constructs sets of audited and not audited properties and classes which behavior has been overridden
|
* Recursively constructs sets of audited and not audited properties and classes which behavior has been overridden
|
||||||
|
@ -309,6 +303,42 @@ public class AuditedPropertiesReader {
|
||||||
return allClassAudited;
|
return allClassAudited;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addPropertiesFromDynamicComponent(DynamicComponentSource dynamicComponentSource) {
|
||||||
|
Audited audited = computeAuditConfiguration(dynamicComponentSource.getXClass());
|
||||||
|
if(!fieldAccessedPersistentProperties.isEmpty()) {
|
||||||
|
//TODO ŁŻ hmm... for sure ?
|
||||||
|
throw new MappingException("Dynamic component cannot have field accessed persistent properties");
|
||||||
|
}
|
||||||
|
for (String property : propertyAccessedPersistentProperties) {
|
||||||
|
// If this is not a persistent property, with the same access type as currently checked,
|
||||||
|
// it's not audited as well.
|
||||||
|
// If the property was already defined by the subclass, is ignored by superclasses
|
||||||
|
String accessType = AccessType.PROPERTY.getType();
|
||||||
|
if (!auditedPropertiesHolder.contains(property)) {
|
||||||
|
final Value propertyValue = persistentPropertiesSource.getProperty(property).getValue();
|
||||||
|
if (propertyValue instanceof Component) {
|
||||||
|
this.addFromComponentProperty(new DynamicProperty(dynamicComponentSource, property), accessType, (Component) propertyValue, audited);
|
||||||
|
} else {
|
||||||
|
this.addFromNotComponentProperty(new DynamicProperty(dynamicComponentSource, property), accessType, audited);
|
||||||
|
}
|
||||||
|
} else if (propertiesGroupMapping.containsKey(property)) {
|
||||||
|
//todo ŁŻ - I'm not sure is that the case that we should handle for dynamic component.
|
||||||
|
// Retrieve embedded component name based on class field.
|
||||||
|
final String embeddedName = propertiesGroupMapping.get(property);
|
||||||
|
if (!auditedPropertiesHolder.contains(embeddedName)) {
|
||||||
|
// Manage properties mapped within <properties> tag.
|
||||||
|
final Value propertyValue = persistentPropertiesSource.getProperty(embeddedName).getValue();
|
||||||
|
this.addFromPropertiesGroup(
|
||||||
|
embeddedName,
|
||||||
|
new DynamicProperty(dynamicComponentSource, property), accessType,
|
||||||
|
(Component) propertyValue,
|
||||||
|
audited
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively adds all audited properties of entity class and its superclasses.
|
* Recursively adds all audited properties of entity class and its superclasses.
|
||||||
*
|
*
|
||||||
|
@ -317,7 +347,7 @@ public class AuditedPropertiesReader {
|
||||||
private void addPropertiesFromClass(XClass clazz) {
|
private void addPropertiesFromClass(XClass clazz) {
|
||||||
final Audited allClassAudited = computeAuditConfiguration( clazz );
|
final Audited allClassAudited = computeAuditConfiguration( clazz );
|
||||||
|
|
||||||
//look in the class
|
//look in the class
|
||||||
addFromProperties(
|
addFromProperties(
|
||||||
clazz.getDeclaredProperties( "field" ),
|
clazz.getDeclaredProperties( "field" ),
|
||||||
"field",
|
"field",
|
||||||
|
@ -346,7 +376,7 @@ public class AuditedPropertiesReader {
|
||||||
Audited allClassAudited) {
|
Audited allClassAudited) {
|
||||||
for ( XProperty property : properties ) {
|
for ( XProperty property : properties ) {
|
||||||
// If this is not a persistent property, with the same access type as currently checked,
|
// If this is not a persistent property, with the same access type as currently checked,
|
||||||
// it's not audited as well.
|
// it's not audited as well.
|
||||||
// If the property was already defined by the subclass, is ignored by superclasses
|
// If the property was already defined by the subclass, is ignored by superclasses
|
||||||
if ( persistentProperties.contains( property.getName() )
|
if ( persistentProperties.contains( property.getName() )
|
||||||
&& !auditedPropertiesHolder.contains( property.getName() ) ) {
|
&& !auditedPropertiesHolder.contains( property.getName() ) ) {
|
||||||
|
@ -404,43 +434,36 @@ public class AuditedPropertiesReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFromComponentProperty(
|
private void addFromComponentProperty(
|
||||||
XProperty property,
|
XProperty property,
|
||||||
String accessType,
|
String accessType,
|
||||||
Component propertyValue,
|
Component propertyValue,
|
||||||
Audited allClassAudited) {
|
Audited allClassAudited) {
|
||||||
final ComponentAuditingData componentData = new ComponentAuditingData();
|
final ComponentAuditingData componentData = new ComponentAuditingData();
|
||||||
final boolean isAudited = fillPropertyData( property, componentData, accessType, allClassAudited );
|
final boolean isAudited = fillPropertyData(property, componentData, accessType, allClassAudited);
|
||||||
|
|
||||||
if ( propertyValue.isDynamic() ) {
|
final PersistentPropertiesSource componentPropertiesSource;
|
||||||
if ( isAudited ) {
|
if (propertyValue.isDynamic() && isAudited) {
|
||||||
throw new MappingException(
|
componentPropertiesSource = new DynamicComponentSource(reflectionManager, propertyValue, property);
|
||||||
"Audited dynamic-component properties are not supported. Consider applying @NotAudited annotation to "
|
} else {
|
||||||
+ propertyValue.getOwner().getEntityName() + "#" + property + "."
|
componentPropertiesSource = new ComponentPropertiesSource(reflectionManager, propertyValue);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource(
|
final ComponentAuditedPropertiesReader audPropReader = new ComponentAuditedPropertiesReader(
|
||||||
reflectionManager, propertyValue
|
ModificationStore.FULL,
|
||||||
);
|
componentPropertiesSource,
|
||||||
|
componentData,
|
||||||
|
globalCfg,
|
||||||
|
reflectionManager,
|
||||||
|
propertyNamePrefix + MappingTools.createComponentPrefix(property.getName())
|
||||||
|
);
|
||||||
|
audPropReader.read();
|
||||||
|
|
||||||
final ComponentAuditedPropertiesReader audPropReader = new ComponentAuditedPropertiesReader(
|
if (isAudited) {
|
||||||
ModificationStore.FULL,
|
// Now we know that the property is audited
|
||||||
componentPropertiesSource,
|
auditedPropertiesHolder.addPropertyAuditingData(property.getName(), componentData);
|
||||||
componentData,
|
}
|
||||||
globalCfg,
|
}
|
||||||
reflectionManager,
|
|
||||||
propertyNamePrefix + MappingTools.createComponentPrefix( property.getName() )
|
|
||||||
);
|
|
||||||
audPropReader.read();
|
|
||||||
|
|
||||||
if ( isAudited ) {
|
|
||||||
// Now we know that the property is audited
|
|
||||||
auditedPropertiesHolder.addPropertyAuditingData( property.getName(), componentData );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFromNotComponentProperty(XProperty property, String accessType, Audited allClassAudited) {
|
private void addFromNotComponentProperty(XProperty property, String accessType, Audited allClassAudited) {
|
||||||
final PropertyAuditingData propertyData = new PropertyAuditingData();
|
final PropertyAuditingData propertyData = new PropertyAuditingData();
|
||||||
|
@ -688,6 +711,11 @@ public class AuditedPropertiesReader {
|
||||||
private final XClass xclass;
|
private final XClass xclass;
|
||||||
private final Component component;
|
private final Component component;
|
||||||
|
|
||||||
|
protected ComponentPropertiesSource(XClass xClazz, Component component) {
|
||||||
|
this.xclass = xClazz;
|
||||||
|
this.component = component;
|
||||||
|
}
|
||||||
|
|
||||||
public ComponentPropertiesSource(ReflectionManager reflectionManager, Component component) {
|
public ComponentPropertiesSource(ReflectionManager reflectionManager, Component component) {
|
||||||
try {
|
try {
|
||||||
this.xclass = reflectionManager.classForName( component.getComponentClassName(), this.getClass() );
|
this.xclass = reflectionManager.classForName( component.getComponentClassName(), this.getClass() );
|
||||||
|
@ -715,4 +743,15 @@ public class AuditedPropertiesReader {
|
||||||
return xclass;
|
return xclass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DynamicComponentSource extends ComponentPropertiesSource {
|
||||||
|
|
||||||
|
private XProperty baseProperty;
|
||||||
|
|
||||||
|
public DynamicComponentSource(ReflectionManager reflectionManager, Component component, XProperty baseProperty) {
|
||||||
|
super(reflectionManager.toXClass(Map.class), component);
|
||||||
|
this.baseProperty = baseProperty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.hibernate.envers.configuration.internal.metadata.reader;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class prenteds to be property but in fact it represents entry in the map (for dynamic component)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
|
*/
|
||||||
|
public class DynamicProperty implements XProperty {
|
||||||
|
|
||||||
|
private AuditedPropertiesReader.DynamicComponentSource source;
|
||||||
|
private String propertyName;
|
||||||
|
|
||||||
|
public DynamicProperty(AuditedPropertiesReader.DynamicComponentSource source, String propertyName) {
|
||||||
|
this.source = source;
|
||||||
|
this.propertyName = propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XClass getDeclaringClass() {
|
||||||
|
return source.getXClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCollection() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Collection> getCollectionClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XClass getType() {
|
||||||
|
return source.getXClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XClass getElementClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XClass getClassOrElementClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XClass getMapKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getModifiers() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAccessible(boolean accessible) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object target, Object... parameters) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTypeResolved() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Annotation> boolean isAnnotationPresent(Class<T> annotationType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Annotation[] getAnnotations() {
|
||||||
|
return new Annotation[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,17 +41,24 @@ import org.hibernate.property.Setter;
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Michal Skowronek (mskowr at o2 dot pl)
|
* @author Michal Skowronek (mskowr at o2 dot pl)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperBuilder {
|
public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperBuilder {
|
||||||
private final PropertyData propertyData;
|
private final PropertyData propertyData;
|
||||||
private final MultiPropertyMapper delegate;
|
private final MultiPropertyMapper delegate;
|
||||||
private final Class componentClass;
|
private final Class componentClass;
|
||||||
|
|
||||||
public ComponentPropertyMapper(PropertyData propertyData, Class componentClass) {
|
public ComponentPropertyMapper(PropertyData propertyData, Class componentClass) {
|
||||||
this.propertyData = propertyData;
|
this.propertyData = propertyData;
|
||||||
this.delegate = new MultiPropertyMapper();
|
//ŁŻ this could be done better
|
||||||
this.componentClass = componentClass;
|
if (Map.class.equals(componentClass)) {
|
||||||
}
|
this.delegate = new MultiDynamicComponentMapper(propertyData);
|
||||||
|
this.componentClass = HashMap.class;
|
||||||
|
} else {
|
||||||
|
this.delegate = new MultiPropertyMapper();
|
||||||
|
this.componentClass = componentClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(PropertyData propertyData) {
|
public void add(PropertyData propertyData) {
|
||||||
|
@ -139,7 +146,7 @@ public class ComponentPropertyMapper implements PropertyMapper, CompositeMapperB
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( allNullAndSingle ) {
|
if ( allNullAndSingle ) {
|
||||||
// single property, but default value need not be null, so we'll set it to null anyway
|
// single property, but default value need not be null, so we'll set it to null anyway
|
||||||
setter.set( obj, null, null );
|
setter.set( obj, null, null );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.hibernate.envers.internal.entities.mapper;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
import org.hibernate.envers.configuration.spi.AuditConfiguration;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi mapper for dynamic components (it knows that component is a map, not a class)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
|
*/
|
||||||
|
public class MultiDynamicComponentMapper extends MultiPropertyMapper {
|
||||||
|
|
||||||
|
private PropertyData dynamicComponentData;
|
||||||
|
|
||||||
|
public MultiDynamicComponentMapper(PropertyData dynamicComponentData) {
|
||||||
|
this.dynamicComponentData = dynamicComponentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mapToMapFromEntity(
|
||||||
|
SessionImplementor session,
|
||||||
|
Map<String, Object> data,
|
||||||
|
Object newObj,
|
||||||
|
Object oldObj) {
|
||||||
|
boolean ret = false;
|
||||||
|
for (PropertyData propertyData : properties.keySet()) {
|
||||||
|
if (newObj == null && oldObj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object newValue = newObj == null ? null : getValue(newObj, propertyData);
|
||||||
|
Object oldValue = oldObj == null ? null : getValue(oldObj, propertyData);
|
||||||
|
|
||||||
|
ret |= properties.get(propertyData).mapToMapFromEntity(session, data, newValue, oldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getValue(Object newObj, PropertyData propertyData) {
|
||||||
|
return ((Map) newObj).get(propertyData.getBeanName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean map(
|
||||||
|
SessionImplementor session,
|
||||||
|
Map<String, Object> data,
|
||||||
|
String[] propertyNames,
|
||||||
|
Object[] newState,
|
||||||
|
Object[] oldState) {
|
||||||
|
boolean ret = false;
|
||||||
|
for (int i = 0; i < propertyNames.length; i++) {
|
||||||
|
final String propertyName = propertyNames[i];
|
||||||
|
Map<String, PropertyData> propertyDatas = getPropertyDatas();
|
||||||
|
if (propertyDatas.containsKey(propertyName)) {
|
||||||
|
final PropertyMapper propertyMapper = properties.get(propertyDatas.get(propertyName));
|
||||||
|
final Object newObj = getAtIndexOrNull(newState, i);
|
||||||
|
final Object oldObj = getAtIndexOrNull(oldState, i);
|
||||||
|
ret |= propertyMapper.mapToMapFromEntity(session, data, newObj, oldObj);
|
||||||
|
propertyMapper.mapModifiedFlagsToMapFromEntity(session, data, newObj, oldObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mapModifiedFlagsToMapFromEntity(
|
||||||
|
SessionImplementor session,
|
||||||
|
Map<String, Object> data,
|
||||||
|
Object newObj,
|
||||||
|
Object oldObj) {
|
||||||
|
for (PropertyData propertyData : properties.keySet()) {
|
||||||
|
if (newObj == null && oldObj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object newValue = newObj == null ? null : getValue(newObj, propertyData);
|
||||||
|
Object oldValue = oldObj == null ? null : getValue(oldObj, propertyData);
|
||||||
|
properties.get(propertyData).mapModifiedFlagsToMapFromEntity(session, data, newValue, oldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mapToEntityFromMap(
|
||||||
|
AuditConfiguration verCfg, Object obj, Map data, Object primaryKey,
|
||||||
|
AuditReaderImplementor versionsReader, Number revision) {
|
||||||
|
Object mapProxy = MapProxyTool.newInstanceOfBeanProxyForMap(generateClassName(data, dynamicComponentData.getBeanName()), (Map) obj, properties.keySet(), verCfg.getClassLoaderService());
|
||||||
|
for (PropertyData propertyData : properties.keySet()) {
|
||||||
|
PropertyMapper mapper = properties.get(propertyData);
|
||||||
|
mapper.mapToEntityFromMap(verCfg, mapProxy, data, primaryKey, versionsReader, revision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateClassName(Map data, String dynamicComponentPropertyName) {
|
||||||
|
return (data.get("$type$") + StringTools.capitalizeFirst(dynamicComponentPropertyName)).replaceAll("_", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ import org.hibernate.property.Getter;
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Michal Skowronek (mskowr at o2 dot pl)
|
* @author Michal Skowronek (mskowr at o2 dot pl)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
public class MultiPropertyMapper implements ExtendedPropertyMapper {
|
public class MultiPropertyMapper implements ExtendedPropertyMapper {
|
||||||
protected final Map<PropertyData, PropertyMapper> properties;
|
protected final Map<PropertyData, PropertyMapper> properties;
|
||||||
|
@ -66,7 +67,7 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper {
|
||||||
return (CompositeMapperBuilder) properties.get( propertyData );
|
return (CompositeMapperBuilder) properties.get( propertyData );
|
||||||
}
|
}
|
||||||
|
|
||||||
final ComponentPropertyMapper componentMapperBuilder = new ComponentPropertyMapper( propertyData, componentClass );
|
final ComponentPropertyMapper componentMapperBuilder = new ComponentPropertyMapper(propertyData, componentClass);
|
||||||
addComposite( propertyData, componentMapperBuilder );
|
addComposite( propertyData, componentMapperBuilder );
|
||||||
|
|
||||||
return componentMapperBuilder;
|
return componentMapperBuilder;
|
||||||
|
@ -78,7 +79,7 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper {
|
||||||
propertyDatas.put( propertyData.getName(), propertyData );
|
propertyDatas.put( propertyData.getName(), propertyData );
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getAtIndexOrNull(Object[] array, int index) {
|
protected Object getAtIndexOrNull(Object[] array, int index) {
|
||||||
return array == null ? null : array[index];
|
return array == null ? null : array[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,4 +224,8 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper {
|
||||||
public Map<PropertyData, PropertyMapper> getProperties() {
|
public Map<PropertyData, PropertyMapper> getProperties() {
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, PropertyData> getPropertyDatas() {
|
||||||
|
return propertyDatas;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
package org.hibernate.envers.internal.tools;
|
||||||
|
|
||||||
|
import javassist.*;
|
||||||
|
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||||
|
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
|
||||||
|
import org.hibernate.envers.internal.entities.PropertyData;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
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 class MapProxyTool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
|
* 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 name 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
|
||||||
|
* @return new instance of proxy
|
||||||
|
*/
|
||||||
|
public static Object newInstanceOfBeanProxyForMap(String name, Map<String, Object> map, Set<PropertyData> propertyDatas, ClassLoaderService classLoaderService) {
|
||||||
|
Class aClass = loadClass(name, classLoaderService);
|
||||||
|
if (aClass == null) {
|
||||||
|
Map<String, Class<?>> properties = prepareProperties(propertyDatas);
|
||||||
|
aClass = generate(name, properties);
|
||||||
|
}
|
||||||
|
return createNewInstance(map, aClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String, Class<?>>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()) {
|
||||||
|
cc.addField(new CtField(resolveCtClass(entry.getValue()), entry.getKey(), cc));
|
||||||
|
|
||||||
|
// 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("}");
|
||||||
|
System.out.println(sb);
|
||||||
|
return CtNewConstructor.make(sb.toString(), cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
|
||||||
|
throws CannotCompileException {
|
||||||
|
|
||||||
|
String getterName = "get" + capitalizeFirst(fieldName);
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append("public ").append(fieldClass.getName()).append(" ")
|
||||||
|
.append(getterName).append("(){").append("return (" + fieldClass.getName() + ")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);
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,51 +27,59 @@ import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
public abstract class StringTools {
|
public abstract class StringTools {
|
||||||
public static boolean isEmpty(String s) {
|
public static boolean isEmpty(String s) {
|
||||||
return s == null || "".equals( s );
|
return s == null || "".equals(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEmpty(Object o) {
|
public static boolean isEmpty(Object o) {
|
||||||
return o == null || "".equals( o );
|
return o == null || "".equals(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param s String, from which to get the last component.
|
* @param s String, from which to get the last component.
|
||||||
*
|
* @return The last component of the dot-separated string <code>s</code>. For example, for a string
|
||||||
* @return The last component of the dot-separated string <code>s</code>. For example, for a string
|
* "a.b.c", the result is "c".
|
||||||
* "a.b.c", the result is "c".
|
*/
|
||||||
*/
|
public static String getLastComponent(String s) {
|
||||||
public static String getLastComponent(String s) {
|
if (s == null) {
|
||||||
if ( s == null ) {
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
final int lastDot = s.lastIndexOf(".");
|
||||||
final int lastDot = s.lastIndexOf( "." );
|
if (lastDot == -1) {
|
||||||
if ( lastDot == -1 ) {
|
return s;
|
||||||
return s;
|
} else {
|
||||||
}
|
return s.substring(lastDot + 1);
|
||||||
else {
|
}
|
||||||
return s.substring( lastDot + 1 );
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To the given string builder, appends all strings in the given iterator, separating them with the given
|
* To the given string builder, appends all strings in the given iterator, separating them with the given
|
||||||
* separator. For example, for an interator "a" "b" "c" and separator ":" the output is "a:b:c".
|
* separator. For example, for an interator "a" "b" "c" and separator ":" the output is "a:b:c".
|
||||||
*
|
*
|
||||||
* @param sb String builder, to which to append.
|
* @param sb String builder, to which to append.
|
||||||
* @param contents Strings to be appended.
|
* @param contents Strings to be appended.
|
||||||
* @param separator Separator between subsequent content.
|
* @param separator Separator between subsequent content.
|
||||||
*/
|
*/
|
||||||
public static void append(StringBuilder sb, Iterator<String> contents, String separator) {
|
public static void append(StringBuilder sb, Iterator<String> contents, String separator) {
|
||||||
boolean isFirst = true;
|
boolean isFirst = true;
|
||||||
while ( contents.hasNext() ) {
|
while (contents.hasNext()) {
|
||||||
if ( !isFirst ) {
|
if (!isFirst) {
|
||||||
sb.append( separator );
|
sb.append(separator);
|
||||||
}
|
}
|
||||||
sb.append( contents.next() );
|
sb.append(contents.next());
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes first letter of the string
|
||||||
|
* @param fieldName
|
||||||
|
* @return capitalized string
|
||||||
|
*/
|
||||||
|
public static String capitalizeFirst(String fieldName) {
|
||||||
|
return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.hibernate.envers.internal.tools;
|
||||||
|
|
||||||
|
import org.hibernate.property.Getter;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MapProxyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerate() throws Exception {
|
||||||
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
map.put("age",14);
|
||||||
|
Map<String, Class<?>> properties = new HashMap<String, Class<?>>();
|
||||||
|
properties.put("age",Integer.class);
|
||||||
|
Class testClass = MapProxyTool.generate("TestClass", properties);
|
||||||
|
Object testClassInstance = testClass.getConstructor(Map.class).newInstance(map);
|
||||||
|
Getter getter = ReflectionTools.getGetter(testClass, "age", "property");
|
||||||
|
Object o = getter.get(testClassInstance);
|
||||||
|
System.out.println(o);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package org.hibernate.envers.test.integration.components.dynamic;
|
||||||
|
|
||||||
|
import org.hibernate.envers.Audited;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Audited
|
||||||
|
public class AuditedDynamicComponentEntity implements Serializable {
|
||||||
|
private long id;
|
||||||
|
private String note;
|
||||||
|
private Map<String, Object> customFields = new HashMap<String, Object>();
|
||||||
|
private SimpleEntity simpleEntity;
|
||||||
|
|
||||||
|
public AuditedDynamicComponentEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuditedDynamicComponentEntity(long id, String note) {
|
||||||
|
this.id = id;
|
||||||
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( !(o instanceof AuditedDynamicComponentEntity) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuditedDynamicComponentEntity that = (AuditedDynamicComponentEntity) o;
|
||||||
|
|
||||||
|
if ( id != that.id ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( customFields != null ? !customFields.equals( that.customFields ) : that.customFields != null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( note != null ? !note.equals( that.note ) : that.note != null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (id ^ (id >>> 32));
|
||||||
|
result = 31 * result + (note != null ? note.hashCode() : 0);
|
||||||
|
result = 31 * result + (customFields != null ? customFields.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AuditedDynamicMapComponent(id = " + id + ", note = " + note + ", customFields = " + customFields + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNote() {
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNote(String note) {
|
||||||
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getCustomFields() {
|
||||||
|
return customFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomFields(Map<String, Object> customFields) {
|
||||||
|
this.customFields = customFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SimpleEntity getSimpleEntity() {
|
||||||
|
return simpleEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSimpleEntity(SimpleEntity simpleEntity) {
|
||||||
|
this.simpleEntity = simpleEntity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +1,203 @@
|
||||||
package org.hibernate.envers.test.integration.components.dynamic;
|
package org.hibernate.envers.test.integration.components.dynamic;
|
||||||
|
|
||||||
import java.io.File;
|
import junit.framework.Assert;
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
|
import org.hibernate.Session;
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.envers.Audited;
|
|
||||||
import org.hibernate.envers.configuration.EnversSettings;
|
import org.hibernate.envers.configuration.EnversSettings;
|
||||||
import org.hibernate.envers.internal.tools.StringTools;
|
import org.hibernate.envers.internal.tools.StringTools;
|
||||||
import org.hibernate.envers.test.AbstractEnversTest;
|
import org.hibernate.envers.query.AuditEntity;
|
||||||
|
import org.hibernate.envers.test.BaseEnversFunctionalTestCase;
|
||||||
|
import org.hibernate.envers.test.Priority;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.hibernate.testing.ServiceRegistryBuilder;
|
import org.hibernate.testing.ServiceRegistryBuilder;
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Lukasz Zuchowski (author at zuchos dot com)
|
||||||
*/
|
*/
|
||||||
@TestForIssue(jiraKey = "HHH-8049")
|
@TestForIssue(jiraKey = "HHH-8049")
|
||||||
public class AuditedDynamicComponentTest extends AbstractEnversTest {
|
public class AuditedDynamicComponentTest extends BaseEnversFunctionalTestCase {
|
||||||
@Test
|
|
||||||
public void testAuditedDynamicComponentFailure() throws URISyntaxException {
|
|
||||||
final Configuration config = new Configuration();
|
|
||||||
final URL hbm = Thread.currentThread().getContextClassLoader().getResource(
|
|
||||||
"mappings/dynamicComponents/mapAudited.hbm.xml"
|
|
||||||
);
|
|
||||||
config.addFile( new File( hbm.toURI() ) );
|
|
||||||
|
|
||||||
final String auditStrategy = getAuditStrategy();
|
@Override
|
||||||
if ( !StringTools.isEmpty( auditStrategy ) ) {
|
protected String[] getMappings() {
|
||||||
config.setProperty( EnversSettings.AUDIT_STRATEGY, auditStrategy );
|
return new String[]{"mappings/dynamicComponents/mapAudited.hbm.xml"};
|
||||||
}
|
}
|
||||||
|
|
||||||
final ServiceRegistry serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( config.getProperties() );
|
//@Test
|
||||||
try {
|
public void testAuditedDynamicComponentFailure() throws URISyntaxException {
|
||||||
config.buildSessionFactory( serviceRegistry );
|
final Configuration config = new Configuration();
|
||||||
Assert.fail( "MappingException expected" );
|
final URL hbm = Thread.currentThread().getContextClassLoader().getResource(
|
||||||
}
|
"mappings/dynamicComponents/mapAudited.hbm.xml"
|
||||||
catch (MappingException e) {
|
);
|
||||||
Assert.assertEquals(
|
config.addFile(new File(hbm.toURI()));
|
||||||
"Audited dynamic-component properties are not supported. Consider applying @NotAudited annotation to "
|
|
||||||
+ AuditedDynamicMapComponent.class.getName() + "#customFields.",
|
final String auditStrategy = getAuditStrategy();
|
||||||
e.getMessage()
|
if (!StringTools.isEmpty(auditStrategy)) {
|
||||||
);
|
config.setProperty(EnversSettings.AUDIT_STRATEGY, auditStrategy);
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
ServiceRegistryBuilder.destroy( serviceRegistry );
|
final ServiceRegistry serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry(config.getProperties());
|
||||||
}
|
try {
|
||||||
}
|
config.buildSessionFactory(serviceRegistry);
|
||||||
|
Assert.fail("MappingException expected");
|
||||||
|
} catch (MappingException e) {
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Audited dynamic-component properties are not supported. Consider applying @NotAudited annotation to "
|
||||||
|
+ AuditedDynamicComponentEntity.class.getName() + "#customFields.",
|
||||||
|
e.getMessage()
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
ServiceRegistryBuilder.destroy(serviceRegistry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Priority(10)
|
||||||
|
public void initData() {
|
||||||
|
Session session = openSession();
|
||||||
|
|
||||||
|
SimpleEntity simpleEntity = new SimpleEntity(1L, "Very simple entity");
|
||||||
|
|
||||||
|
// Revision 1
|
||||||
|
session.getTransaction().begin();
|
||||||
|
session.save(simpleEntity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 2
|
||||||
|
session.getTransaction().begin();
|
||||||
|
AuditedDynamicComponentEntity entity = new AuditedDynamicComponentEntity(1L, "static field value");
|
||||||
|
entity.getCustomFields().put("prop1", 13);
|
||||||
|
entity.getCustomFields().put("prop2", 0.1f);
|
||||||
|
entity.getCustomFields().put("prop3", simpleEntity);
|
||||||
|
session.save(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// revision 3
|
||||||
|
session.getTransaction().begin();
|
||||||
|
SimpleEntity simpleEntity2 = new SimpleEntity(2L, "Not so simple entity");
|
||||||
|
session.save(simpleEntity2);
|
||||||
|
entity = (AuditedDynamicComponentEntity) session.get(AuditedDynamicComponentEntity.class, entity.getId());
|
||||||
|
entity.getCustomFields().put("prop3", simpleEntity2);
|
||||||
|
session.update(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 4
|
||||||
|
session.getTransaction().begin();
|
||||||
|
entity = (AuditedDynamicComponentEntity) session.get(AuditedDynamicComponentEntity.class, entity.getId());
|
||||||
|
entity.getCustomFields().put("prop1", 2);
|
||||||
|
session.update(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 5
|
||||||
|
session.getTransaction().begin();
|
||||||
|
entity = (AuditedDynamicComponentEntity) session.load(AuditedDynamicComponentEntity.class, entity.getId());
|
||||||
|
entity.getCustomFields().remove("prop2");
|
||||||
|
session.update(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 6
|
||||||
|
session.getTransaction().begin();
|
||||||
|
entity = (AuditedDynamicComponentEntity) session.load(AuditedDynamicComponentEntity.class, entity.getId());
|
||||||
|
entity.getCustomFields().clear();
|
||||||
|
session.update(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 7
|
||||||
|
session.getTransaction().begin();
|
||||||
|
entity = (AuditedDynamicComponentEntity) session.load(AuditedDynamicComponentEntity.class, entity.getId());
|
||||||
|
session.delete(entity);
|
||||||
|
session.getTransaction().commit();
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevisionsCounts() {
|
||||||
|
Assert.assertEquals(
|
||||||
|
Arrays.asList(2, 3, 4, 5,6,7),
|
||||||
|
getAuditReader().getRevisions(AuditedDynamicComponentEntity.class, 1L)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHistoryOfId1() {
|
||||||
|
// Revision 2
|
||||||
|
AuditedDynamicComponentEntity entity = new AuditedDynamicComponentEntity(1L, "static field value");
|
||||||
|
entity.getCustomFields().put("prop1", 13);
|
||||||
|
entity.getCustomFields().put("prop2", 0.1f);
|
||||||
|
entity.getCustomFields().put("prop3", new SimpleEntity(1L, "Very simple entity"));
|
||||||
|
AuditedDynamicComponentEntity ver2 = getAuditReader().find(
|
||||||
|
AuditedDynamicComponentEntity.class,
|
||||||
|
entity.getId(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
Assert.assertEquals(entity, ver2);
|
||||||
|
|
||||||
|
// Revision 3
|
||||||
|
SimpleEntity simpleEntity2 = new SimpleEntity(2L, "Not so simple entity");
|
||||||
|
entity.getCustomFields().put("prop3", simpleEntity2);
|
||||||
|
AuditedDynamicComponentEntity ver3 = getAuditReader().find(
|
||||||
|
AuditedDynamicComponentEntity.class,
|
||||||
|
entity.getId(),
|
||||||
|
3
|
||||||
|
);
|
||||||
|
Assert.assertEquals(entity, ver3);
|
||||||
|
|
||||||
|
// Revision 4
|
||||||
|
entity.getCustomFields().put("prop1", 2);
|
||||||
|
AuditedDynamicComponentEntity ver4 = getAuditReader().find(
|
||||||
|
AuditedDynamicComponentEntity.class,
|
||||||
|
entity.getId(),
|
||||||
|
4
|
||||||
|
);
|
||||||
|
Assert.assertEquals(entity, ver4);
|
||||||
|
|
||||||
|
// Revision 5
|
||||||
|
entity.getCustomFields().put("prop2",null);
|
||||||
|
AuditedDynamicComponentEntity ver5 = getAuditReader().find(
|
||||||
|
AuditedDynamicComponentEntity.class,
|
||||||
|
entity.getId(),
|
||||||
|
5
|
||||||
|
);
|
||||||
|
Assert.assertEquals(entity, ver5);
|
||||||
|
|
||||||
|
// Revision 5
|
||||||
|
entity.getCustomFields().put("prop1",null);
|
||||||
|
entity.getCustomFields().put("prop2",null);
|
||||||
|
entity.getCustomFields().put("prop3",null);
|
||||||
|
AuditedDynamicComponentEntity ver6 = getAuditReader().find(
|
||||||
|
AuditedDynamicComponentEntity.class,
|
||||||
|
entity.getId(),
|
||||||
|
6
|
||||||
|
);
|
||||||
|
Assert.assertEquals(entity, ver6);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//@Test
|
||||||
|
public void testOfQueryOnDynamicComponent() {
|
||||||
|
//given (and result of initData()
|
||||||
|
AuditedDynamicComponentEntity entity = new AuditedDynamicComponentEntity(1L, "static field value");
|
||||||
|
entity.getCustomFields().put("prop1", 13);
|
||||||
|
entity.getCustomFields().put("prop2", 0.1f);
|
||||||
|
|
||||||
|
//when
|
||||||
|
List ver1 = getAuditReader().createQuery()
|
||||||
|
.forEntitiesAtRevision(AuditedDynamicComponentEntity.class, 2)
|
||||||
|
.add(AuditEntity.property("customFields_prop1").le(20))
|
||||||
|
.getResultList();
|
||||||
|
|
||||||
|
//then
|
||||||
|
Assert.assertEquals(entity, ver1.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Audited
|
|
||||||
public static class AuditedDynamicMapComponent implements Serializable {
|
|
||||||
public long id;
|
|
||||||
public String note;
|
|
||||||
public Map<String, Object> customFields = new HashMap<String, Object>(); // Invalid audited dynamic-component.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.hibernate.envers.test.integration.components.dynamic;
|
||||||
|
|
||||||
|
import org.hibernate.envers.Audited;
|
||||||
|
|
||||||
|
@Audited
|
||||||
|
public class SimpleEntity {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String simpleProperty;
|
||||||
|
|
||||||
|
public SimpleEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleEntity(Long id, String simpleProperty) {
|
||||||
|
this.id = id;
|
||||||
|
this.simpleProperty = simpleProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSimpleProperty() {
|
||||||
|
return simpleProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSimpleProperty(String simpleProperty) {
|
||||||
|
this.simpleProperty = simpleProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof SimpleEntity)) return false;
|
||||||
|
|
||||||
|
SimpleEntity that = (SimpleEntity) o;
|
||||||
|
|
||||||
|
if (id != null ? !id.equals(that.id) : that.id != null) return false;
|
||||||
|
if (simpleProperty != null ? !simpleProperty.equals(that.simpleProperty) : that.simpleProperty != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = id != null ? id.hashCode() : 0;
|
||||||
|
result = 31 * result + (simpleProperty != null ? simpleProperty.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SimpleEntity{" +
|
||||||
|
"id=" + id +
|
||||||
|
", simpleProperty='" + simpleProperty + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,19 @@
|
||||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||||
<hibernate-mapping default-lazy="false">
|
<hibernate-mapping default-lazy="false">
|
||||||
<class name="org.hibernate.envers.test.integration.components.dynamic.AuditedDynamicComponentTest$AuditedDynamicMapComponent">
|
<class name="org.hibernate.envers.test.integration.components.dynamic.AuditedDynamicComponentEntity" table="Audited">
|
||||||
<id name="id" type="long" column="id" access="field"/>
|
<id name="id" type="long" column="id"/>
|
||||||
<property name="note" type="string" access="field"/>
|
<property name="note" type="string"/>
|
||||||
<dynamic-component name="customFields" access="field">
|
<dynamic-component name="customFields">
|
||||||
<property name="prop1" type="integer"/>
|
<property name="prop1" type="integer"/>
|
||||||
<property name="prop2" type="float"/>
|
<property name="prop2" type="float"/>
|
||||||
|
<many-to-one name="prop3" class="org.hibernate.envers.test.integration.components.dynamic.SimpleEntity" column="SIMPLE_ID"/>
|
||||||
</dynamic-component>
|
</dynamic-component>
|
||||||
</class>
|
</class>
|
||||||
|
<class name="org.hibernate.envers.test.integration.components.dynamic.SimpleEntity" table="simple_abc">
|
||||||
|
<id name="id" type="long" column="id"/>
|
||||||
|
<property name="simpleProperty" type="string"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
</hibernate-mapping>
|
</hibernate-mapping>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue