/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.entities.mapper.relation;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.entities.mapper.PropertyMapper;
import org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.reader.AuditReaderImplementor;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.property.Setter;
/**
* @author Adam Warski (adam at warski dot org)
* @author Michal Skowronek (mskowr at o2 dot pl)
*/
public abstract class AbstractCollectionMapper<T> implements PropertyMapper {
protected final CommonCollectionMapperData commonCollectionMapperData;
protected final Class<? extends T> collectionClass;
private final Constructor<? extends T> proxyConstructor;
protected AbstractCollectionMapper(CommonCollectionMapperData commonCollectionMapperData,
Class<? extends T> collectionClass, Class<? extends T> proxyClass) {
this.commonCollectionMapperData = commonCollectionMapperData;
this.collectionClass = collectionClass;
try {
proxyConstructor = proxyClass.getConstructor(Initializor.class);
} catch (NoSuchMethodException e) {
throw new AuditException(e);
}
}
protected abstract Collection getNewCollectionContent(PersistentCollection newCollection);
protected abstract Collection getOldCollectionContent(Serializable oldCollection);
/**
* Maps the changed collection element to the given map.
* @param data Where to map the data.
* @param changed The changed collection element to map.
*/
protected abstract void mapToMapFromObject(Map<String, Object> data, Object changed);
private void addCollectionChanges(List<PersistentCollectionChangeData> collectionChanges, Set<Object> changed,
RevisionType revisionType, Serializable id) {
for (Object changedObj : changed) {
Map<String, Object> entityData = new HashMap<String, Object>();
Map<String, Object> originalId = new HashMap<String, Object>();
entityData.put(commonCollectionMapperData.getVerEntCfg().getOriginalIdPropName(), originalId);
collectionChanges.add(new PersistentCollectionChangeData(
commonCollectionMapperData.getVersionsMiddleEntityName(), entityData, changedObj));
// Mapping the collection owner's id.
commonCollectionMapperData.getReferencingIdData().getPrefixedMapper().mapToMapFromId(originalId, id);
// Mapping collection element and index (if present).
mapToMapFromObject(originalId, changedObj);
entityData.put(commonCollectionMapperData.getVerEntCfg().getRevisionTypePropName(), revisionType);
}
}
@SuppressWarnings({"unchecked"})
public List<PersistentCollectionChangeData> mapCollectionChanges(String referencingPropertyName,
PersistentCollection newColl,
Serializable oldColl, Serializable id) {
if (!commonCollectionMapperData.getCollectionReferencingPropertyData().getName()
.equals(referencingPropertyName)) {
return null;
}
List<PersistentCollectionChangeData> collectionChanges = new ArrayList<PersistentCollectionChangeData>();
// Comparing new and old collection content.
Collection newCollection = getNewCollectionContent(newColl);
Collection oldCollection = getOldCollectionContent(oldColl);
Set<Object> added = new HashSet<Object>();
if (newColl != null) { added.addAll(newCollection); }
// Re-hashing the old collection as the hash codes of the elements there may have changed, and the
// removeAll in AbstractSet has an implementation that is hashcode-change sensitive (as opposed to addAll).
if (oldColl != null) { added.removeAll(new HashSet(oldCollection)); }
addCollectionChanges(collectionChanges, added, RevisionType.ADD, id);
Set<Object> deleted = new HashSet<Object>();
if (oldColl != null) { deleted.addAll(oldCollection); }
// The same as above - re-hashing new collection.
if (newColl != null) { deleted.removeAll(new HashSet(newCollection)); }
addCollectionChanges(collectionChanges, deleted, RevisionType.DEL, id);
return collectionChanges;
}
public boolean mapToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj, Object oldObj) {
// Changes are mapped in the "mapCollectionChanges" method.
return false;
}
@Override
public void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj, Object oldObj) {
PropertyData propertyData = commonCollectionMapperData.getCollectionReferencingPropertyData();
if (propertyData.isUsingModifiedFlag()) {
if(isFromNullToEmptyOrFromEmptyToNull((PersistentCollection) newObj, (Serializable) oldObj)){
data.put(propertyData.getModifiedFlagPropertyName(), true);
} else {
List<PersistentCollectionChangeData> changes = mapCollectionChanges(
commonCollectionMapperData.getCollectionReferencingPropertyData().getName(),
(PersistentCollection) newObj, (Serializable) oldObj, null);
data.put(propertyData.getModifiedFlagPropertyName(), !changes.isEmpty());
}
}
}
private boolean isFromNullToEmptyOrFromEmptyToNull(PersistentCollection newColl, Serializable oldColl) {
// Comparing new and old collection content.
Collection newCollection = getNewCollectionContent(newColl);
Collection oldCollection = getOldCollectionContent(oldColl);
return oldCollection == null && newCollection != null && newCollection.isEmpty()
|| newCollection == null && oldCollection != null && oldCollection.isEmpty();
}
@Override
public void mapModifiedFlagsToMapForCollectionChange(String collectionPropertyName, Map<String, Object> data) {
PropertyData propertyData = commonCollectionMapperData.getCollectionReferencingPropertyData();
if (propertyData.isUsingModifiedFlag()) {
data.put(propertyData.getModifiedFlagPropertyName(), propertyData.getName().equals(collectionPropertyName));
}
}
protected abstract Initializor<T> getInitializor(AuditConfiguration verCfg,
AuditReaderImplementor versionsReader, Object primaryKey,
Number revision);
public void mapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey,
AuditReaderImplementor versionsReader, Number revision) {
Setter setter = ReflectionTools.getSetter(obj.getClass(),
commonCollectionMapperData.getCollectionReferencingPropertyData());
try {
setter.set(obj, proxyConstructor.newInstance(getInitializor(verCfg, versionsReader, primaryKey, revision)), null);
} catch (InstantiationException e) {
throw new AuditException(e);
} catch (IllegalAccessException e) {
throw new AuditException(e);
} catch (InvocationTargetException e) {
throw new AuditException(e);
}
}
}