Java tutorial
/* * 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.configuration.internal; import java.util.Date; import java.util.Set; import javax.persistence.Column; import org.hibernate.MappingException; import org.hibernate.annotations.common.reflection.ClassLoadingException; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.envers.Audited; import org.hibernate.envers.DefaultRevisionEntity; import org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity; import org.hibernate.envers.ModifiedEntityNames; import org.hibernate.envers.RevisionEntity; import org.hibernate.envers.RevisionListener; import org.hibernate.envers.RevisionNumber; import org.hibernate.envers.RevisionTimestamp; import org.hibernate.envers.configuration.internal.metadata.AuditTableData; import org.hibernate.envers.configuration.internal.metadata.MetadataTools; import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; import org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity; import org.hibernate.envers.internal.entities.PropertyData; import org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator; import org.hibernate.envers.internal.revisioninfo.DefaultTrackingModifiedEntitiesRevisionInfoGenerator; import org.hibernate.envers.internal.revisioninfo.ModifiedEntityNamesReader; import org.hibernate.envers.internal.revisioninfo.RevisionInfoGenerator; import org.hibernate.envers.internal.revisioninfo.RevisionInfoNumberReader; import org.hibernate.envers.internal.revisioninfo.RevisionInfoQueryCreator; import org.hibernate.envers.internal.tools.MutableBoolean; import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.type.LongType; import org.hibernate.type.Type; import org.dom4j.Document; import org.dom4j.Element; /** * @author Adam Warski (adam at warski dot org) * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class RevisionInfoConfiguration { private String revisionInfoEntityName; private PropertyData revisionInfoIdData; private PropertyData revisionInfoTimestampData; private PropertyData modifiedEntityNamesData; private Type revisionInfoTimestampType; private GlobalConfiguration globalCfg; private String revisionPropType; private String revisionPropSqlType; public RevisionInfoConfiguration(GlobalConfiguration globalCfg) { this.globalCfg = globalCfg; if (globalCfg.isUseRevisionEntityWithNativeId()) { revisionInfoEntityName = "org.hibernate.envers.DefaultRevisionEntity"; } else { revisionInfoEntityName = "org.hibernate.envers.enhanced.SequenceIdRevisionEntity"; } revisionInfoIdData = new PropertyData("id", "id", "field", null); revisionInfoTimestampData = new PropertyData("timestamp", "timestamp", "field", null); modifiedEntityNamesData = new PropertyData("modifiedEntityNames", "modifiedEntityNames", "field", null); revisionInfoTimestampType = new LongType(); revisionPropType = "integer"; } private Document generateDefaultRevisionInfoXmlMapping() { final Document document = XMLHelper.getDocumentFactory().createDocument(); final Element classMapping = MetadataTools.createEntity(document, new AuditTableData(null, null, globalCfg.getDefaultSchemaName(), globalCfg.getDefaultCatalogName()), null, null); classMapping.addAttribute("name", revisionInfoEntityName); classMapping.addAttribute("table", "REVINFO"); final Element idProperty = MetadataTools.addNativelyGeneratedId(classMapping, revisionInfoIdData.getName(), revisionPropType, globalCfg.isUseRevisionEntityWithNativeId()); MetadataTools.addColumn(idProperty, "REV", null, null, null, null, null, null, false); final Element timestampProperty = MetadataTools.addProperty(classMapping, revisionInfoTimestampData.getName(), revisionInfoTimestampType.getName(), true, false); MetadataTools.addColumn(timestampProperty, "REVTSTMP", null, null, null, null, null, null, false); if (globalCfg.isTrackEntitiesChangedInRevision()) { generateEntityNamesTrackingTableMapping(classMapping, "modifiedEntityNames", globalCfg.getDefaultSchemaName(), globalCfg.getDefaultCatalogName(), "REVCHANGES", "REV", "ENTITYNAME", "string"); } return document; } /** * Generates mapping that represents a set of primitive types.<br /> * <code> * <set name="propertyName" table="joinTableName" schema="joinTableSchema" catalog="joinTableCatalog" * cascade="persist, delete" lazy="false" fetch="join"><br /> * <key column="joinTablePrimaryKeyColumnName" /><br /> * <element type="joinTableValueColumnType"><br /> * <column name="joinTableValueColumnName" /><br /> * </element><br /> * </set> * </code> */ private void generateEntityNamesTrackingTableMapping(Element classMapping, String propertyName, String joinTableSchema, String joinTableCatalog, String joinTableName, String joinTablePrimaryKeyColumnName, String joinTableValueColumnName, String joinTableValueColumnType) { final Element set = classMapping.addElement("set"); set.addAttribute("name", propertyName); set.addAttribute("table", joinTableName); set.addAttribute("schema", joinTableSchema); set.addAttribute("catalog", joinTableCatalog); set.addAttribute("cascade", "persist, delete"); set.addAttribute("fetch", "join"); set.addAttribute("lazy", "false"); final Element key = set.addElement("key"); key.addAttribute("column", joinTablePrimaryKeyColumnName); final Element element = set.addElement("element"); element.addAttribute("type", joinTableValueColumnType); final Element column = element.addElement("column"); column.addAttribute("name", joinTableValueColumnName); } private Element generateRevisionInfoRelationMapping() { final Document document = XMLHelper.getDocumentFactory().createDocument(); final Element revRelMapping = document.addElement("key-many-to-one"); revRelMapping.addAttribute("type", revisionPropType); revRelMapping.addAttribute("class", revisionInfoEntityName); if (revisionPropSqlType != null) { // Putting a fake name to make Hibernate happy. It will be replaced later anyway. MetadataTools.addColumn(revRelMapping, "*", null, null, null, revisionPropSqlType, null, null, false); } return revRelMapping; } private void searchForRevisionInfoCfgInProperties(XClass clazz, ReflectionManager reflectionManager, MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound, MutableBoolean modifiedEntityNamesFound, String accessType) { for (XProperty property : clazz.getDeclaredProperties(accessType)) { final RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class); final RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class); final ModifiedEntityNames modifiedEntityNames = property.getAnnotation(ModifiedEntityNames.class); if (revisionNumber != null) { if (revisionNumberFound.isSet()) { throw new MappingException("Only one property may be annotated with @RevisionNumber!"); } final XClass revisionNumberClass = property.getType(); if (reflectionManager.equals(revisionNumberClass, Integer.class) || reflectionManager.equals(revisionNumberClass, Integer.TYPE)) { revisionInfoIdData = new PropertyData(property.getName(), property.getName(), accessType, null); revisionNumberFound.set(); } else if (reflectionManager.equals(revisionNumberClass, Long.class) || reflectionManager.equals(revisionNumberClass, Long.TYPE)) { revisionInfoIdData = new PropertyData(property.getName(), property.getName(), accessType, null); revisionNumberFound.set(); // The default is integer revisionPropType = "long"; } else { throw new MappingException("The field annotated with @RevisionNumber must be of type " + "int, Integer, long or Long"); } // Getting the @Column definition of the revision number property, to later use that info to // generate the same mapping for the relation from an audit table's revision number to the // revision entity revision number. final Column revisionPropColumn = property.getAnnotation(Column.class); if (revisionPropColumn != null) { revisionPropSqlType = revisionPropColumn.columnDefinition(); } } if (revisionTimestamp != null) { if (revisionTimestampFound.isSet()) { throw new MappingException("Only one property may be annotated with @RevisionTimestamp!"); } final XClass revisionTimestampClass = property.getType(); if (reflectionManager.equals(revisionTimestampClass, Long.class) || reflectionManager.equals(revisionTimestampClass, Long.TYPE) || reflectionManager.equals(revisionTimestampClass, Date.class) || reflectionManager.equals(revisionTimestampClass, java.sql.Date.class)) { revisionInfoTimestampData = new PropertyData(property.getName(), property.getName(), accessType, null); revisionTimestampFound.set(); } else { throw new MappingException("The field annotated with @RevisionTimestamp must be of type " + "long, Long, java.util.Date or java.sql.Date"); } } if (modifiedEntityNames != null) { if (modifiedEntityNamesFound.isSet()) { throw new MappingException("Only one property may be annotated with @ModifiedEntityNames!"); } final XClass modifiedEntityNamesClass = property.getType(); if (reflectionManager.equals(modifiedEntityNamesClass, Set.class) && reflectionManager.equals(property.getElementClass(), String.class)) { modifiedEntityNamesData = new PropertyData(property.getName(), property.getName(), accessType, null); modifiedEntityNamesFound.set(); } else { throw new MappingException( "The field annotated with @ModifiedEntityNames must be of Set<String> type."); } } } } private void searchForRevisionInfoCfg(XClass clazz, ReflectionManager reflectionManager, MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound, MutableBoolean modifiedEntityNamesFound) { final XClass superclazz = clazz.getSuperclass(); if (!"java.lang.Object".equals(superclazz.getName())) { searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound); } searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound, "field"); searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound, "property"); } public RevisionInfoConfigurationResult configure(MetadataImplementor metadata, ReflectionManager reflectionManager) { boolean revisionEntityFound = false; RevisionInfoGenerator revisionInfoGenerator = null; Class<?> revisionInfoClass = null; for (PersistentClass persistentClass : metadata.getEntityBindings()) { // Ensure we're in POJO, not dynamic model, mapping. if (persistentClass.getClassName() != null) { XClass clazz; try { clazz = reflectionManager.classForName(persistentClass.getClassName()); } catch (ClassLoadingException e) { throw new MappingException(e); } final RevisionEntity revisionEntity = clazz.getAnnotation(RevisionEntity.class); if (revisionEntity != null) { if (revisionEntityFound) { throw new MappingException("Only one entity may be annotated with @RevisionEntity!"); } // Checking if custom revision entity isn't audited if (clazz.getAnnotation(Audited.class) != null) { throw new MappingException("An entity annotated with @RevisionEntity cannot be audited!"); } revisionEntityFound = true; final MutableBoolean revisionNumberFound = new MutableBoolean(); final MutableBoolean revisionTimestampFound = new MutableBoolean(); final MutableBoolean modifiedEntityNamesFound = new MutableBoolean(); searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound); if (!revisionNumberFound.isSet()) { throw new MappingException( "An entity annotated with @RevisionEntity must have a field annotated " + "with @RevisionNumber!"); } if (!revisionTimestampFound.isSet()) { throw new MappingException( "An entity annotated with @RevisionEntity must have a field annotated " + "with @RevisionTimestamp!"); } revisionInfoEntityName = persistentClass.getEntityName(); revisionInfoClass = persistentClass.getMappedClass(); final Class<? extends RevisionListener> revisionListenerClass = getRevisionListenerClass( revisionEntity.value()); revisionInfoTimestampType = persistentClass.getProperty(revisionInfoTimestampData.getName()) .getType(); if (globalCfg.isTrackEntitiesChangedInRevision() || (globalCfg.isUseRevisionEntityWithNativeId() && DefaultTrackingModifiedEntitiesRevisionEntity.class .isAssignableFrom(revisionInfoClass)) || (!globalCfg.isUseRevisionEntityWithNativeId() && SequenceIdTrackingModifiedEntitiesRevisionEntity.class .isAssignableFrom(revisionInfoClass)) || modifiedEntityNamesFound.isSet()) { // If tracking modified entities parameter is enabled, custom revision info entity is a subtype // of DefaultTrackingModifiedEntitiesRevisionEntity class, or @ModifiedEntityNames annotation is used. revisionInfoGenerator = new DefaultTrackingModifiedEntitiesRevisionInfoGenerator( revisionInfoEntityName, revisionInfoClass, revisionListenerClass, revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData); globalCfg.setTrackEntitiesChangedInRevision(true); } else { revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass, revisionListenerClass, revisionInfoTimestampData, isTimestampAsDate()); } } } } // In case of a custom revision info generator, the mapping will be null. Document revisionInfoXmlMapping = null; final Class<? extends RevisionListener> revisionListenerClass = getRevisionListenerClass( RevisionListener.class); if (revisionInfoGenerator == null) { if (globalCfg.isTrackEntitiesChangedInRevision()) { revisionInfoClass = globalCfg.isUseRevisionEntityWithNativeId() ? DefaultTrackingModifiedEntitiesRevisionEntity.class : SequenceIdTrackingModifiedEntitiesRevisionEntity.class; revisionInfoEntityName = revisionInfoClass.getName(); revisionInfoGenerator = new DefaultTrackingModifiedEntitiesRevisionInfoGenerator( revisionInfoEntityName, revisionInfoClass, revisionListenerClass, revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData); } else { revisionInfoClass = globalCfg.isUseRevisionEntityWithNativeId() ? DefaultRevisionEntity.class : SequenceIdRevisionEntity.class; revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass, revisionListenerClass, revisionInfoTimestampData, isTimestampAsDate()); } revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping(); } return new RevisionInfoConfigurationResult(revisionInfoGenerator, revisionInfoXmlMapping, new RevisionInfoQueryCreator(revisionInfoEntityName, revisionInfoIdData.getName(), revisionInfoTimestampData.getName(), isTimestampAsDate()), generateRevisionInfoRelationMapping(), new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData), globalCfg.isTrackEntitiesChangedInRevision() ? new ModifiedEntityNamesReader(revisionInfoClass, modifiedEntityNamesData) : null, revisionInfoEntityName, revisionInfoClass, revisionInfoTimestampData); } private boolean isTimestampAsDate() { final String typename = revisionInfoTimestampType.getName(); return "date".equals(typename) || "time".equals(typename) || "timestamp".equals(typename); } /** * @param defaultListener Revision listener that shall be applied if {@code org.hibernate.envers.revision_listener} * parameter has not been set. * * @return Revision listener. */ private Class<? extends RevisionListener> getRevisionListenerClass( Class<? extends RevisionListener> defaultListener) { if (globalCfg.getRevisionListenerClass() != null) { return globalCfg.getRevisionListenerClass(); } return defaultListener; } }