Package org.hibernate.envers.configuration.metadata.reader

Source Code of org.hibernate.envers.configuration.metadata.reader.AuditedPropertiesReader$ComponentPropertiesSource

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<String> propertyAccessedPersistentProperties;
  private final Set<String> fieldAccessedPersistentProperties;
  // Mapping class field to corresponding <properties> element.
  private final Map<String, String> propertiesGroupMapping;

  private final Set<XProperty> overriddenAuditedProperties;
  private final Set<XProperty> overriddenNotAuditedProperties;

  private final Set<XClass> overriddenAuditedClasses;
  private final Set<XClass> 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<AuditOverride> 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<AuditOverride> 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<Property> 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 <properties> 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<Property> 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<XProperty> properties, String accessType, Set<String> 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 <properties> 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<AuditOverride> 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<? extends Annotation> 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<? extends Annotation> 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<Property> getPropertyIterator() { return component.getPropertyIterator(); }
    public Property getProperty(String propertyName) { return component.getProperty(propertyName); }
    public XClass getXClass() { return xclass; }
  }
}
TOP

Related Classes of org.hibernate.envers.configuration.metadata.reader.AuditedPropertiesReader$ComponentPropertiesSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.