6 Replies Latest reply on Oct 3, 2009 1:52 PM by Adam Warski

    interesting behaviour and compatibility question

    Sharath Komarla Newbie

      Hi.

      I am researching using Envers for our audit requirements and it has been very helpful so far. I've run into something "interesting" and I'm not sure if this is expected or not.

      env:
      Hibernate 3.3.2.GA
      Envers 1.2.1.GA-hibernate-3.3
      Spring Framework 2.5.6.. though not really important for this post.

      Here is my use code..:

      // dummied down version of class for illustration
      @Audited
      public class Node {
       private Integer id;
       private Integer objectVersion;
      
       @Audited
       private NodeStatus status;
      
       @Audited
       private Fee fee;
      
       private String name;
       private String description;
       private Date createDate;
       private Date statusDate;
      
       // snip getters setters and other attrs
      }
      
      // Node Status is
      public class NodeStatus extends Status {
       private static Map<String,NodeStatus> statusMap = new HashMap<String,NodeStatus>();
       private static final String ACTIVE="Active";
       private static final String SUSPENDED="Suspended";
       private static final String INACTIVE="Inactive";
      
       static {
       statusMap.put(INACTIVE, new NodeStatus(15, INACTIVE));
       statusMap.put(ACTIVE, new NodeStatus(16, ACTIVE));
       statusMap.put(SUSPENDED, new NodeStatus(17, SUSPENDED));
       }
      
       NodeStatus() {
       super();
       }
       private NodeStatus(Integer id, String name) {
       super(id, name);
       }
       public static NodeStatus Active() {
       return statusMap.get(ACTIVE);
       }
       public static NodeStatus Inactive() {
       return statusMap.get(INACTIVE);
       }
       public static NodeStatus Suspended() {
       return statusMap.get(SUSPENDED);
       }
      
       // snip hashCode, equals, toString etc.
      }
      
      public abstract class Status extends Base implements LookUp {
      
       public Status() {
       super();
       }
      
       public Status(Integer id, String name) {
       super();
       this.id = id;
       this.name = name;
       }
       private Integer id;
       private String name;
       private String description;
      
       // getters + setters snipped
      }
      
      // Base is an extention of LightEntity from Gilead (pretty nice toolset)
      public abstract class Base extends LightEntity implements Serializable {
       // nothing here for now
      }
      
      // Lookup is a marker interface for other use...
      /**
       * Interface for a domain object that is mapped to a Look Up table
       */
      public interface LookUp {
       public Integer getId();
       public String getName();
      }
      
      
      public class Fee implements Serializable{
       private static final long serialVersionUID = -1895794651654982575L;
      
       private Float value;
       private String unit;
      
       public Fee() {}
       public Fee(Float value, String unit) {
       setUnit(unit);
       setValue(value);
       }
      
       // snip hashCode, equals, toString etc.
      }
      


      now the mapping (done in xml)
      <?xml version="1.0"?>
      <!DOCTYPE hibernate-mapping PUBLIC
       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
      
      <hibernate-mapping default-access="field">
       <class name="com.sterling.ifn.model.Node" table="IFN_NODE">
       <id name="id" type="int">
       <column name="IFN_Node_ID"/>
       <generator class="identity"/>
       </id>
       <version name="objectVersion" type="int">
       <column name="Row_Version" not-null="true"/>
       </version>
       <component name="fee">
       <property name="value" type="float">
       <column name="Node_Fee"/>
       </property>
       <property name="unit" type="UnitOfTimeType">
       <column name="Fee_Unit_ID"/>
       </property>
       </component>
       <many-to-one name="status" fetch="select"
       class="com.sterling.ifn.model.NodeStatus">
       <column name="IFN_Status_ID"/>
       </many-to-one>
       <property name="name" type="string">
       <column name="Node_NME" length="128" unique="true"/>
       </property>
       <property name="description" type="string">
       <column name="Node_Desc" length="256"/>
       </property>
       <property name="createDate" type="timestamp">
       <column name="Create_DTE" length="23"/>
       </property>
       <property name="statusDate" type="timestamp">
       <column name="Status_DTE" length="23"/>
       </property>
       </class>
      </hibernate-mapping>
      


      Running a unit test with the above.. I get the expected exception

      
      Caused by: org.hibernate.MappingException: An audited relation to a non-audited entity com.sterling.ifn.model.NodeStatus!
       at org.hibernate.envers.configuration.metadata.ToOneRelationMetadataGenerator.addToOne(ToOneRelationMetadataGenerator.java:59)
       at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.addValue(AuditMetadataGenerator.java:126)
       at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.addProperties(AuditMetadataGenerator.java:161)
       at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.generateSecondPass(AuditMetadataGenerator.java:405)
       at org.hibernate.envers.configuration.EntitiesConfigurator.configure(EntitiesConfigurator.java:96)
       at org.hibernate.envers.configuration.AuditConfiguration.<init>(AuditConfiguration.java:86)
       at org.hibernate.envers.configuration.AuditConfiguration.getFor(AuditConfiguration.java:99)
       at org.hibernate.envers.event.AuditEventListener.initialize(AuditEventListener.java:260)
       at org.hibernate.event.EventListeners$1.processListener(EventListeners.java:198)
       at org.hibernate.event.EventListeners.processListeners(EventListeners.java:181)
       at org.hibernate.event.EventListeners.initializeListeners(EventListeners.java:194)
       ... 58 more
      
      


      I add @Audited to NodeStatus definition to see it works out ok.. (but NodeStatus is not really an entity! there is no hibernate mapping for it.
      I get this exception..

      Caused by: org.hibernate.HibernateException: could not init listeners
       at org.hibernate.event.EventListeners.initializeListeners(EventListeners.java:205)
       at org.hibernate.cfg.Configuration.getInitializedEventListeners(Configuration.java:1352)
       at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1341)
       at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:867)
       at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:814)
       at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:732)
       at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:211)
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1369)
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1335)
       ... 50 more
      Caused by: java.lang.NullPointerException
       at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.generateInheritanceMappingData(AuditMetadataGenerator.java:305)
       at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:349)
       at org.hibernate.envers.configuration.EntitiesConfigurator.configure(EntitiesConfigurator.java:87)
       at org.hibernate.envers.configuration.AuditConfiguration.<init>(AuditConfiguration.java:86)
       at org.hibernate.envers.configuration.AuditConfiguration.getFor(AuditConfiguration.java:99)
       at org.hibernate.envers.event.AuditEventListener.initialize(AuditEventListener.java:260)
       at org.hibernate.event.EventListeners$1.processListener(EventListeners.java:198)
       at org.hibernate.event.EventListeners.processListeners(EventListeners.java:181)
       at org.hibernate.event.EventListeners.initializeListeners(EventListeners.java:194)
       ... 58 more
      


      Ok.. maybe I need to mark Base as @Audited. .. and it worked, but I ended up with a IFN_STATUS_AUD table which is not very ideal, especially since it is just a look up table, and will never get updated

      So, I have two things I need confirmation about:

      1) I didn't have to annotate the Fee class because it is just a component
      2) How come I didn't have to annotate Base (NodeStatus extends Status extends Base?


      Regarding why I needed to annotate NodeStatus, it is probably related to the discussion in this thread:
      http://www.jboss.org/index.html?module=bb&op=viewtopic&t=157846&postdays=0&postorder=asc&start=10

      Ideally, I would not annotate the NodeStatus class, but only annotate the association.. So, I see that a patch was submitted and applied to 3.5.0.Beta-1. So will envers from 3.5.0.Beta-1 be compatible with Hibernate 3.3.2?
      If so, I can give that a go.

      Thanks for the great work!