All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.envers.configuration.metadata.reader.AuditedPropertiesReader Maven / Gradle / Ivy

There is a newer version: 7.0.0.Beta1
Show newest version
package org.hibernate.envers.configuration.metadata.reader;
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 javax.persistence.JoinColumn;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Version;

import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.envers.AuditJoinTable;
import org.hibernate.envers.AuditMappedBy;
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.GlobalConfiguration;
import org.hibernate.envers.configuration.metadata.MetadataTools;
import org.hibernate.envers.tools.MappingTools;
import org.hibernate.envers.tools.StringTools;
import org.hibernate.envers.tools.Tools;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;

import static org.hibernate.envers.tools.Tools.newHashMap;
import static org.hibernate.envers.tools.Tools.newHashSet;

/**
 * Reads persistent properties form a
 * {@link org.hibernate.envers.configuration.metadata.reader.PersistentPropertiesSource}
 * and adds the ones that are audited to a
 * {@link org.hibernate.envers.configuration.metadata.reader.AuditedPropertiesHolder},
 * filling all the auditing data.
 * @author Adam Warski (adam at warski dot org)
 * @author Erik-Berndt Scheper
 * @author Hern&aacut;n Chanfreau
 * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
 * @author Michal Skowronek (mskowr at o2 dot pl)
 */
public class AuditedPropertiesReader {
	protected final ModificationStore defaultStore;
	private final PersistentPropertiesSource persistentPropertiesSource;
	private final AuditedPropertiesHolder auditedPropertiesHolder;
	private final GlobalConfiguration globalCfg;
	private final ReflectionManager reflectionManager;
	private final String propertyNamePrefix;

	private final Set propertyAccessedPersistentProperties;
	private final Set fieldAccessedPersistentProperties;
	// Mapping class field to corresponding  element.
	private final Map propertiesGroupMapping;

	private final Set overriddenAuditedProperties;
	private final Set overriddenNotAuditedProperties;

	private final Set overriddenAuditedClasses;
	private final Set overriddenNotAuditedClasses;

	public AuditedPropertiesReader(ModificationStore defaultStore,
								   PersistentPropertiesSource persistentPropertiesSource,
								   AuditedPropertiesHolder auditedPropertiesHolder,
								   GlobalConfiguration globalCfg,
								   ReflectionManager reflectionManager,
								   String propertyNamePrefix) {
		this.defaultStore = defaultStore;
		this.persistentPropertiesSource = persistentPropertiesSource;
		this.auditedPropertiesHolder = auditedPropertiesHolder;
		this.globalCfg = globalCfg;
		this.reflectionManager = reflectionManager;
		this.propertyNamePrefix = propertyNamePrefix;

		propertyAccessedPersistentProperties = newHashSet();
		fieldAccessedPersistentProperties = newHashSet();
		propertiesGroupMapping = newHashMap();

		overriddenAuditedProperties = newHashSet();
		overriddenNotAuditedProperties = newHashSet();

		overriddenAuditedClasses = newHashSet();
		overriddenNotAuditedClasses = newHashSet();
	}

	public void read() {
		// First reading the access types for the persistent properties.
		readPersistentPropertiesAccess();

        // Retrieve classes and properties that are explicitly marked for auditing process by any superclass
        // of currently mapped entity or itself.
        XClass clazz = persistentPropertiesSource.getXClass();
        readAuditOverrides(clazz);

        // Adding all properties from the given class.
        addPropertiesFromClass(clazz);
	}

    /**
     * Recursively constructs sets of audited and not audited properties and classes which behavior has been overridden
     * using {@link AuditOverride} annotation.
     * @param clazz Class that is being processed. Currently mapped entity shall be passed during first invocation.
     */
    private void readAuditOverrides(XClass clazz) {
        /* TODO: Code to remove with @Audited.auditParents - start. */
        Audited allClassAudited = clazz.getAnnotation(Audited.class);
        if (allClassAudited != null && allClassAudited.auditParents().length > 0) {
            for (Class c : allClassAudited.auditParents()) {
                XClass parentClass = reflectionManager.toXClass(c);
                checkSuperclass(clazz, parentClass);
                if (!overriddenNotAuditedClasses.contains(parentClass)) {
                    // If the class has not been marked as not audited by the subclass.
                    overriddenAuditedClasses.add(parentClass);
                }
            }
        }
        /* TODO: Code to remove with @Audited.auditParents - finish. */
        List auditOverrides = computeAuditOverrides(clazz);
        for (AuditOverride auditOverride : auditOverrides) {
            if (auditOverride.forClass() != void.class) {
                XClass overrideClass = reflectionManager.toXClass(auditOverride.forClass());
                checkSuperclass(clazz, overrideClass);
                String propertyName = auditOverride.name();
                if (!StringTools.isEmpty(propertyName)) {
                    // Override @Audited annotation on property level.
                    XProperty property = getProperty(overrideClass, propertyName);
                    if (auditOverride.isAudited()) {
                        if (!overriddenNotAuditedProperties.contains(property)) {
                            // If the property has not been marked as not audited by the subclass.
                            overriddenAuditedProperties.add(property);
                        }
                    } else {
                        if (!overriddenAuditedProperties.contains(property)) {
                            // If the property has not been marked as audited by the subclass.
                            overriddenNotAuditedProperties.add(property);
                        }
                    }
                } else {
                    // Override @Audited annotation on class level.
                    if (auditOverride.isAudited()) {
                        if (!overriddenNotAuditedClasses.contains(overrideClass)) {
                            // If the class has not been marked as not audited by the subclass.
                            overriddenAuditedClasses.add(overrideClass);
                        }
                    } else {
                        if (!overriddenAuditedClasses.contains(overrideClass)) {
                            // If the class has not been marked as audited by the subclass.
                            overriddenNotAuditedClasses.add(overrideClass);
                        }
                    }
                }
            }
        }
        XClass superclass = clazz.getSuperclass();
        if (!clazz.isInterface() && !Object.class.getName().equals(superclass.getName())) {
            readAuditOverrides(superclass);
        }
    }

    /**
     * @param clazz Source class.
     * @return List of @AuditOverride annotations applied at class level.
     */
    private List computeAuditOverrides(XClass clazz) {
        AuditOverrides auditOverrides = clazz.getAnnotation(AuditOverrides.class);
        AuditOverride auditOverride = clazz.getAnnotation(AuditOverride.class);
        if (auditOverrides == null && auditOverride != null) {
            return Arrays.asList(auditOverride);
        } else if (auditOverrides != null && auditOverride == null) {
            return Arrays.asList(auditOverrides.value());
        } else if (auditOverrides != null && auditOverride != null) {
            throw new MappingException("@AuditOverrides annotation should encapsulate all @AuditOverride declarations. " +
                                       "Please revise Envers annotations applied to class " + clazz.getName() + ".");
        }
        return Collections.EMPTY_LIST;
    }

    /**
     * Checks whether one class is assignable from another. If not {@link MappingException} is thrown.
     * @param child Subclass.
     * @param parent Superclass.
     */
    private void checkSuperclass(XClass child, XClass parent) {
        if (!parent.isAssignableFrom(child)) {
            throw new MappingException("Class " + parent.getName() + " is not assignable from " + child.getName() + ". " +
                                       "Please revise Envers annotations applied to " + child.getName() + " type.");
        }
    }

    /**
     * Checks whether class contains property with a given name. If not {@link MappingException} is thrown.
     * @param clazz Class.
     * @param propertyName Property name.
     * @return Property object.
     */
    private XProperty getProperty(XClass clazz, String propertyName) {
        XProperty property = Tools.getProperty(clazz, propertyName);
        if (property == null) {
            throw new MappingException("Property '" + propertyName + "' not found in class " + clazz.getName() + ". " +
                                       "Please revise Envers annotations applied to class " + persistentPropertiesSource.getXClass() + ".");
        }
        return property;
    }

	private void readPersistentPropertiesAccess() {
		Iterator propertyIter = persistentPropertiesSource.getPropertyIterator();
		while (propertyIter.hasNext()) {
			Property property = (Property) propertyIter.next();
			addPersistentProperty(property);
			if ("embedded".equals(property.getPropertyAccessorName()) && property.getName().equals(property.getNodeName())) {
				// If property name equals node name and embedded accessor type is used, processing component
				// has been defined with  tag. See HHH-6636 JIRA issue.
				createPropertiesGroupMapping(property);
			}
		}
	}

    private void addPersistentProperty(Property property) {
        if ("field".equals(property.getPropertyAccessorName())) {
            fieldAccessedPersistentProperties.add(property.getName());
        } else {
            propertyAccessedPersistentProperties.add(property.getName());
        }
    }

    private void createPropertiesGroupMapping(Property property) {
        Component component = (Component) property.getValue();
        Iterator componentProperties = component.getPropertyIterator();
        while (componentProperties.hasNext()) {
            Property componentProperty = componentProperties.next();
            propertiesGroupMapping.put(componentProperty.getName(), component.getNodeName());
        }
    }

    /**
     * @param clazz Class which properties are currently being added.
     * @return {@link Audited} annotation of specified class. If processed type hasn't been explicitly marked, method
     *         checks whether given class exists in {@link AuditedPropertiesReader#overriddenAuditedClasses} collection.
     *         In case of success, {@link Audited} configuration of currently mapped entity is returned, otherwise
     *         {@code null}. If processed type exists in {@link AuditedPropertiesReader#overriddenNotAuditedClasses}
     *         collection, the result is also {@code null}.
     */
    private Audited computeAuditConfiguration(XClass clazz) {
        Audited allClassAudited = clazz.getAnnotation(Audited.class);
        // If processed class is not explicitly marked with @Audited annotation, check whether auditing is
        // forced by any of its child entities configuration (@AuditedOverride.forClass).
        if (allClassAudited == null && overriddenAuditedClasses.contains(clazz)) {
            // Declared audited parent copies @Audited.modStore and @Audited.targetAuditMode configuration from
            // currently mapped entity.
            allClassAudited = persistentPropertiesSource.getXClass().getAnnotation(Audited.class);
            if (allClassAudited == null) {
                // If parent class declares @Audited on the field/property level.
                allClassAudited = DEFAULT_AUDITED;
            }
        } else if (allClassAudited != null && overriddenNotAuditedClasses.contains(clazz)) {
            return null;
        }
        return allClassAudited;
    }

    /**
     * Recursively adds all audited properties of entity class and its superclasses.
     * @param clazz Currently processed class.
     */
	private void addPropertiesFromClass(XClass clazz)  {
		Audited allClassAudited = computeAuditConfiguration(clazz);

		//look in the class
		addFromProperties(clazz.getDeclaredProperties("field"), "field", fieldAccessedPersistentProperties, allClassAudited);
		addFromProperties(clazz.getDeclaredProperties("property"), "property", propertyAccessedPersistentProperties, allClassAudited);
		
		if(allClassAudited != null || !auditedPropertiesHolder.isEmpty()) {
			XClass superclazz = clazz.getSuperclass();
			if (!clazz.isInterface() && !"java.lang.Object".equals(superclazz.getName())) {
				addPropertiesFromClass(superclazz);
			}
		}
	}

	private void addFromProperties(Iterable properties, String accessType, Set persistentProperties, Audited allClassAudited) {
		for (XProperty property : properties) {
			// 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
			if ((persistentProperties.contains(property.getName()) && (!auditedPropertiesHolder
					.contains(property.getName())))) {
				Value propertyValue = persistentPropertiesSource.getProperty(property.getName()).getValue();
				if (propertyValue instanceof Component) {
					this.addFromComponentProperty(property, accessType, (Component)propertyValue, allClassAudited);
				} else {
					this.addFromNotComponentProperty(property, accessType, allClassAudited);
				}
			} else if (propertiesGroupMapping.containsKey(property.getName())) {
				// Retrieve embedded component name based on class field.
				final String embeddedName = propertiesGroupMapping.get(property.getName());
				if (!auditedPropertiesHolder.contains(embeddedName)) {
					// Manage properties mapped within  tag.
					Value propertyValue = persistentPropertiesSource.getProperty(embeddedName).getValue();
					this.addFromPropertiesGroup(embeddedName, property, accessType, (Component)propertyValue, allClassAudited);
				}
			}
		}
	}

	private void addFromPropertiesGroup(String embeddedName, XProperty property, String accessType, Component propertyValue,
										Audited allClassAudited) {
		ComponentAuditingData componentData = new ComponentAuditingData();
		boolean isAudited = fillPropertyData(property, componentData, accessType, allClassAudited);
		if (isAudited) {
			// EntityPersister.getPropertyNames() returns name of embedded component instead of class field.
			componentData.setName(embeddedName);
			// Marking component properties as placed directly in class (not inside another component).
			componentData.setBeanName(null);

			PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource((Component) propertyValue);
			AuditedPropertiesReader audPropReader = new AuditedPropertiesReader(
					ModificationStore.FULL, componentPropertiesSource, componentData, globalCfg, reflectionManager,
					propertyNamePrefix + MappingTools.createComponentPrefix(embeddedName)
			);
			audPropReader.read();

			auditedPropertiesHolder.addPropertyAuditingData(embeddedName, componentData);
		}
	}
	
	private void addFromComponentProperty(XProperty property,
			String accessType, Component propertyValue, Audited allClassAudited) {

		ComponentAuditingData componentData = new ComponentAuditingData();
		boolean isAudited = fillPropertyData(property, componentData, accessType,
				allClassAudited);

		PersistentPropertiesSource componentPropertiesSource = new ComponentPropertiesSource(
				(Component) propertyValue);
		
		ComponentAuditedPropertiesReader audPropReader = new ComponentAuditedPropertiesReader(
				ModificationStore.FULL, componentPropertiesSource,
				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){
		PropertyAuditingData propertyData = new PropertyAuditingData();
		boolean isAudited = fillPropertyData(property, propertyData, accessType, allClassAudited);

		if (isAudited) {
			// Now we know that the property is audited
			auditedPropertiesHolder.addPropertyAuditingData(property.getName(), propertyData);
		}
	}
	
	
	/**
	 * Checks if a property is audited and if yes, fills all of its data.
	 * @param property Property to check.
	 * @param propertyData Property data, on which to set this property's modification store.
	 * @param accessType Access type for the property.
	 * @return False if this property is not audited.
	 */
	private boolean fillPropertyData(XProperty property, PropertyAuditingData propertyData,
									 String accessType, Audited allClassAudited) {

		// check if a property is declared as not audited to exclude it
		// useful if a class is audited but some properties should be excluded
		NotAudited unVer = property.getAnnotation(NotAudited.class);
		if ((unVer != null && !overriddenAuditedProperties.contains(property)) || overriddenNotAuditedProperties.contains(property)) {
			return false;
		} else {
			// if the optimistic locking field has to be unversioned and the current property
			// is the optimistic locking field, don't audit it
			if (globalCfg.isDoNotAuditOptimisticLockingField()) {
				Version jpaVer = property.getAnnotation(Version.class);
				if (jpaVer != null) {
					return false;
				}
			}
		}

		
		if(!this.checkAudited(property, propertyData, allClassAudited)){
			return false;
		}

		String propertyName = propertyNamePrefix + property.getName();
		propertyData.setName(propertyName);
		propertyData.setModifiedFlagName(
                MetadataTools.getModifiedFlagPropertyName(
                        propertyName,
                        globalCfg.getModifiedFlagSuffix()));
		propertyData.setBeanName(property.getName());
		propertyData.setAccessType(accessType);

		addPropertyJoinTables(property, propertyData);
		addPropertyAuditingOverrides(property, propertyData);
		if (!processPropertyAuditingOverrides(property, propertyData)) {
			return false; // not audited due to AuditOverride annotation
		}
		addPropertyMapKey(property, propertyData);
        setPropertyAuditMappedBy(property, propertyData);

		return true;
	}


	protected boolean checkAudited(XProperty property,
			PropertyAuditingData propertyData, Audited allClassAudited) {
		// Checking if this property is explicitly audited or if all properties are.
		Audited aud = (property.isAnnotationPresent(Audited.class)) ? (property.getAnnotation(Audited.class)) : allClassAudited;
		if (aud == null && overriddenAuditedProperties.contains(property) && !overriddenNotAuditedProperties.contains(property)) {
			// Assigning @Audited defaults. If anyone needs to customize those values in the future,
			// appropriate fields shall be added to @AuditOverride annotation.
			aud = DEFAULT_AUDITED;
		}
		if (aud != null) {
			propertyData.setStore(aud.modStore());
			propertyData.setRelationTargetAuditMode(aud.targetAuditMode());
			propertyData.setUsingModifiedFlag(checkUsingModifiedFlag(aud));
			return true;
		} else {
			return false;
		}
	}

	protected boolean checkUsingModifiedFlag(Audited aud) {
		return globalCfg.hasSettingForUsingModifiedFlag() ?
				globalCfg.isGlobalWithModifiedFlag() : aud.withModifiedFlag();
	}

	private void setPropertyAuditMappedBy(XProperty property, PropertyAuditingData propertyData) {
        OneToMany oneToMany = property.getAnnotation(OneToMany.class);
        if (oneToMany != null && !"".equals(oneToMany.mappedBy())) {
            propertyData.setAuditMappedBy(oneToMany.mappedBy());
        }
        AuditMappedBy auditMappedBy = property.getAnnotation(AuditMappedBy.class);
        if (auditMappedBy != null) {
		    propertyData.setAuditMappedBy(auditMappedBy.mappedBy());
            if (!"".equals(auditMappedBy.positionMappedBy())) {
                propertyData.setPositionMappedBy(auditMappedBy.positionMappedBy());
            }
        }
    }

	private void addPropertyMapKey(XProperty property, PropertyAuditingData propertyData) {
		MapKey mapKey = property.getAnnotation(MapKey.class);
		if (mapKey != null) {
			propertyData.setMapKey(mapKey.name());
		}
	}

	private void addPropertyJoinTables(XProperty property, PropertyAuditingData propertyData) {
		// first set the join table based on the AuditJoinTable annotation
		AuditJoinTable joinTable = property.getAnnotation(AuditJoinTable.class);
		if (joinTable != null) {
			propertyData.setJoinTable(joinTable);
		} else {
			propertyData.setJoinTable(DEFAULT_AUDIT_JOIN_TABLE);
		}
	}

	/***
	 * Add the {@link org.hibernate.envers.AuditOverride} annotations.
	 *
	 * @param property the property being processed
	 * @param propertyData the Envers auditing data for this property
	 */
	private void addPropertyAuditingOverrides(XProperty property, PropertyAuditingData propertyData) {
		AuditOverride annotationOverride = property.getAnnotation(AuditOverride.class);
		if (annotationOverride != null) {
			propertyData.addAuditingOverride(annotationOverride);
		}
		AuditOverrides annotationOverrides = property.getAnnotation(AuditOverrides.class);
		if (annotationOverrides != null) {
			propertyData.addAuditingOverrides(annotationOverrides);
		}
	}

	/**
	 * Process the {@link org.hibernate.envers.AuditOverride} annotations for this property.
	 *
	 * @param property
	 *            the property for which the {@link org.hibernate.envers.AuditOverride}
	 *            annotations are being processed
	 * @param propertyData
	 *            the Envers auditing data for this property
	 * @return {@code false} if isAudited() of the override annotation was set to
	 */
	private boolean processPropertyAuditingOverrides(XProperty property, PropertyAuditingData propertyData) {
		// if this property is part of a component, process all override annotations
		if (this.auditedPropertiesHolder instanceof ComponentAuditingData) {
			List overrides = ((ComponentAuditingData) this.auditedPropertiesHolder).getAuditingOverrides();
			for (AuditOverride override : overrides) {
				if (property.getName().equals(override.name())) {
					// the override applies to this property
					if (!override.isAudited()) {
						return false;
					} else {
						if (override.auditJoinTable() != null) {
							propertyData.setJoinTable(override.auditJoinTable());
						}
					}
				}
			}
			
		}
		return true;
	}

    private static Audited DEFAULT_AUDITED = new Audited() {
        public ModificationStore modStore() { return ModificationStore.FULL; }
        public RelationTargetAuditMode targetAuditMode() { return RelationTargetAuditMode.AUDITED; }
        public Class[] auditParents() { return new Class[0]; }
        public boolean withModifiedFlag() { return false; }
        public Class annotationType() { return this.getClass(); }
    };

	private static AuditJoinTable DEFAULT_AUDIT_JOIN_TABLE = new AuditJoinTable() {
		public String name() { return ""; }
		public String schema() { return ""; }
		public String catalog() { return ""; }
		public JoinColumn[] inverseJoinColumns() { return new JoinColumn[0]; }
		public Class annotationType() { return this.getClass(); }
	};

    private class ComponentPropertiesSource implements PersistentPropertiesSource {
		private final XClass xclass;
		private final Component component;

		private ComponentPropertiesSource(Component component) {
			try {
				this.xclass = reflectionManager.classForName(component.getComponentClassName(), this.getClass());
			} catch (ClassNotFoundException e) {
				throw new MappingException(e);
			}

			this.component = component;
		}

		@SuppressWarnings({"unchecked"})
		public Iterator getPropertyIterator() { return component.getPropertyIterator(); }
		public Property getProperty(String propertyName) { return component.getProperty(propertyName); }
		public XClass getXClass() { return xclass; }
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy