1 2 3 Previous Next 30 Replies Latest reply on Sep 9, 2012 1:35 PM by Adam Warski

    adding more information to revision entity

    Vyacheslav Sakhno Newbie

      I need information about which object in what table changed and what kind of change happened to store in revision entity.

       

      I have revision listener with username set, but don't understand how to get other info.

       

      public class CustomRevisionListener implements RevisionListener {

       

                public void newRevision(Object revisionEntity) {

                          CustomRevisionEntity revision = (CustomRevisionEntity) revisionEntity;

              revision.setUsername(getUsername());

       

      //              String username;

      //              String entityId;

      //              Integer revisionType;

      //              String entityName;

       

       

                }

       

       

                public String getUsername() {

              final SecurityContext context = SecurityContextHolder.getContext();

              if (context != null) {

                  if (context.getAuthentication() != null) {

                            return context.getAuthentication().getName();

                  } else {

                            return "anonymous";

                  }

              }

              return "anonymous";

                }

       

      I need to store entityId and entityName because i need to query for a number of tables/types of entities in one request.

       

      As far as i see envers doesn't provide such functionality. How can i get it?

       

      Can i use EntityTrackingRevisionListener or some kind of other listener?

        • 1. Re: adding more information to revision entity
          Vyacheslav Sakhno Newbie

          I need at least functionalitity of https://hibernate.onjira.com/browse/HHH-5580

          but indeed i need to access entityId(ids) or better the object(objects) saved itself in a listener to build and save set of properties changes of entity in a table. Instead of quering for previous revision all the time.

           


          How to get access to saved entity data? Will this possibility be implemented?

           

          And one more question about REVCHANGES table?

           

          REV  ENTITYNAME
          2----------------------------------------
          354  org.hibernate.envers.demo.Person
          454  org.hibernate.envers.demo.Address

           

          What if we save 2-10 Persons entities saved in one transaction? How will REVCHANGES look like?

           

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person

          54  org.hibernate.envers.demo.Person


          ?


          • 2. Re: adding more information to revision entity
            Adam Warski Master

            Well you can't get that info using the current public APIs. But why would you need this functionality?

             

            As in the issue you linked (which is already implemented and released), you can store the names of modified entities. Then you will have to query for the objects changed in each entity type separately anyway. So I don't see what a list of ids would additionaly give you?

             

            Adam

            • 3. Re: adding more information to revision entity
              Vyacheslav Sakhno Newbie

              It is not about only id of entities.

              It is about perfomance issue and goal of getting list of property changes for each revision of object. Now for this i need to 1) query revision, +2) query previous revision 3) generate changeset in memory for each object change. +4) apply order by. I want to store property changes(from value, to value) for each object changed in revision, but now i need to use hibernate interceptor to store them in db simultaneously with Envers audit framework which seems strange.

               

              Imho auditing is about "property changes" so it would be nice to have at least previous revision linked to revision somehow without additional query to database. It is about number of queries to get info now, to make joins of tables in database instead of hibernate.

               

              I also received access to both entityName and entityId using EntityTrackingRevisionListener.

              And now using additional queries to database to build property changes set.

               

              Storing ids of entities won't help much for join here, but i need to store group entity name and group entity id. I have graph of objects with root object calles group entity. When looking for root object property changes i want to see list of all changes not only in object but in @OneToOne relations to the object, so i store groupObjectName and groupObjectId in EntityChange and use hibernate query to find changes of the whole group.

               

              @Entity

              @Table(name = "ENTITY_CHANGED_IN_REVISION")

              public class EntityChange {

               

               

                        @Id

                  @NotNull

                  @GeneratedValue

                  @Column(name = "ID")

                  private Long id;

               

               

                        @Column(name="GROUP_NAME")

                        @Size(max = 50)

                  private String groupName;

               

               

                        @Column(name="GROUP_ENTITY_ID")

                        @Size(max = 50)

                        private String groupEntityId;

               

               

                        @Column(name="ENTITY_ID")

                        @Size(max = 50)

                        private String entityId;

               

               

                        @Column(name="ENTITY_NAME")

                        private String entityName;

               

               

                        @Column(name="REVISION_TYPE")

                        @Size(min = 0, max = 2)

                        private Integer revisionType;

               

                        @ManyToOne(fetch = FetchType.LAZY, optional = false)

                        @JoinColumn(name = "REV_ID", updatable = false)

                        private Revision revision;

               

               

              //          @OneToMany(fetch=FetchType.LAZY, mappedBy="logRecord", cascade = {CascadeType.ALL}, orphanRemoval = true)

                        @Transient

                        private List<AuditLogChangeset> propertyChangesSet = new ArrayList<AuditLogChangeset>(0);

              • 4. Re: adding more information to revision entity
                Steve Mactaggart Newbie

                I'm trying to do the same thing..  Envers is really great at showing me the revisions of a given entity, but it doesn't easily let me know what is changed in each revision.

                 

                I have used the EntityTrackingRevisionListener to partially get my solution, while now I can track the table and ID of the change, I really need to intercept the actual entity so I can log other information to make my Revision reporting much faster and more reliable.

                 

                I have attempted to override some of the logic inside the Audit processing listeners, but there are no simple methods to override, or the elements are final so is hard to replace.

                 

                Any suggestions on how to achieve this outcome?

                • 5. Re: adding more information to revision entity
                  Vyacheslav Sakhno Newbie

                  i'm quering for entity by entityName-entityClass and entityId now.

                   

                  package ru.csbi.registry.services.impl.envers;

                   

                   

                  import java.io.Serializable;

                   

                   

                  import org.hibernate.envers.EntityTrackingRevisionListener;

                  import org.hibernate.envers.RevisionType;

                  import org.springframework.beans.BeansException;

                  import org.springframework.context.ApplicationContext;

                  import org.springframework.context.ApplicationContextAware;

                  import org.springframework.security.core.context.SecurityContext;

                  import org.springframework.security.core.context.SecurityContextHolder;

                  import org.springframework.stereotype.Service;

                   

                   

                  import ru.csbi.registry.domain.envers.Revision;

                  import ru.csbi.registry.domain.envers.EntityChange;

                  import ru.csbi.registry.utils.PersistenceManagerHibernate;

                  import ru.csbi.registry.utils.audit.Auditable;

                   

                   

                  @Service

                  public class CustomRevisionListener implements EntityTrackingRevisionListener, ApplicationContextAware {

                   

                   

                            private static ApplicationContext applicationContext;

                   

                            private PersistenceManagerHibernate persistenceManagerHibernate;

                   

                   

                            @Override

                            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

                                      CustomRevisionListener.applicationContext = applicationContext;

                            }

                   

                   

                            public PersistenceManagerHibernate getBean() {

                                      if (persistenceManagerHibernate == null) {

                                                persistenceManagerHibernate = applicationContext.getBean(PersistenceManagerHibernate.class);

                                      }

                                      return persistenceManagerHibernate;

                            }

                   

                   

                            public void newRevision(Object revisionEntity) {

                                      Revision revision = (Revision) revisionEntity;

                          revision.setUsername(getUsername());

                            }

                   

                   

                            public String getUsername() {

                          final SecurityContext context = SecurityContextHolder.getContext();

                          if (context != null) {

                              if (context.getAuthentication() != null) {

                                        return context.getAuthentication().getName();

                              } else {

                                        return "anonymous";

                              }

                          }

                          return "anonymous";

                            }

                   

                   

                            @Override

                            public void entityChanged(@SuppressWarnings("rawtypes") Class entityClass, String entityName, Serializable entityId, RevisionType revisionType, Object revisionEntity) {

                                      Revision revision = (Revision) revisionEntity;

                                      EntityChange entityChangedInRevision = create(entityClass, entityName, entityId, revisionType, revision);

                                      getBean().save(entityChangedInRevision);

                                      revision.getEntitiesChangedInRevision().add(entityChangedInRevision);

                                      getBean().save(revision);

                            }

                   

                   

                            @SuppressWarnings("unchecked")

                            public EntityChange create(@SuppressWarnings("rawtypes") Class entityClass, String entityName, Serializable entityId, RevisionType revisionType, Revision revision) {

                                      EntityChange entityChange = new EntityChange();

                                      entityChange.setEntityId(entityId.toString());

                                      entityChange.setEntityName(entityName);

                                      entityChange.setRevisionType((int)revisionType.getRepresentation());

                                      entityChange.setRevision(revision);

                  // query here

                                      Auditable auditable = null;

                                      if (entityId instanceof Long) {

                                                auditable = (Auditable) getBean().findById(entityClass, (Long)entityId);

                                      } else {

                                                auditable = (Auditable) getBean().findByPk(entityClass, entityId);

                                      }

                                      entityChange.setGroupName(auditable.getAuditGroupName());

                                      entityChange.setGroupEntityId(auditable.getAuditGroupId());

                                      return entityChange;

                            }

                  }

                  • 6. Re: adding more information to revision entity
                    Steve Mactaggart Newbie

                    I agree that would work, but it seems silly to re-query the entity when it actually exists only 2 stack hops up from the entityChanged method..

                     

                    I will try and work on some simple changes to the current Audit classes that will allow overriding of the current methods to support what I think would be required.

                    • 7. Re: adding more information to revision entity
                      Adam Warski Master

                      Ah, the problem of "entity groups" is a well-known one.

                      Well, I can only say Envers is far from being complete

                       

                      Adam

                      • 8. Re: adding more information to revision entity
                        Adam Warski Master

                        If you have some ideas on making Envers more flexible, let me know - plus a pull request will make it much faster to introduce

                         

                        Adam

                        • 9. Re: adding more information to revision entity
                          Steve Mactaggart Newbie

                          I've forked the current codebase and have started looking at what mods I would like to make to get the actual entity, or at least the changed properties during the audit event.

                           

                          But when I attempt to run my application against this codebase in Eclipse I get:

                           

                          java.lang.ExceptionInInitializerError

                                    at org.hibernate.envers.configuration.EntitiesConfigurator.configure(EntitiesConfigurator.java:69)

                                    at org.hibernate.envers.configuration.AuditConfiguration.<init>(AuditConfiguration.java:103)

                                    at org.hibernate.envers.configuration.AuditConfiguration.getFor(AuditConfiguration.java:135)

                                    at org.hibernate.envers.event.EnversIntegrator.integrate(EnversIntegrator.java:63)

                                    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:295)

                                    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)

                                    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1775)

                                    at au.com.clarity.dbstate.CompanyConnectionManager.initSessionFactory(CompanyConnectionManager.java:129)

                                    at au.com.clarity.dbstate.CompanyConnectionManager.<init>(CompanyConnectionManager.java:45)

                                    at au.com.clarity.dbstate.impl.BaseDBManagementProvider.buildCompanyDataSource(BaseDBManagementProvider.java:25)

                                    at au.com.clarity.dbstate.CompanyDatabaseManager.getCompanyConnectionManager(CompanyDatabaseManager.java:189)

                                    at au.com.clarity.dbstate.CompanyDatabaseManager.sessionFactory(CompanyDatabaseManager.java:324)

                                    at au.com.clarity.dbstate.CompanyDatabaseManager.sessionFactory(CompanyDatabaseManager.java:311)

                                    at au.com.clarity.data.dao.HibernateUtil.getCurrentSession(HibernateUtil.java:30)

                                    at au.com.clarity.data.test.transaction.TransactionDocumentVoidTest.setup(TransactionDocumentVoidTest.java:69)

                                    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

                                    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

                                    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

                                    at java.lang.reflect.Method.invoke(Unknown Source)

                                    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

                                    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)

                                    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)

                                    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)

                                    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)

                                    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)

                                    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)

                                    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)

                                    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)

                                    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)

                                    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

                          Caused by: java.lang.IllegalArgumentException: Invalid logger interface org.hibernate.envers.internal.EnversMessageLogger (implementation not found)

                                    at org.jboss.logging.Logger.getMessageLogger(Logger.java:2250)

                                    at org.jboss.logging.Logger.getMessageLogger(Logger.java:2214)

                                    at org.hibernate.envers.configuration.ClassesAuditingData.<clinit>(ClassesAuditingData.java:22)

                                    ... 30 more

                           

                           

                          I've googled but can't find anything that points me in the right direction of why this is happening.

                          • 10. Re: adding more information to revision entity
                            Steve Mactaggart Newbie

                            I've hacked together something that works for me, but I know it probably breaks some rules of API changes.

                            I wanted to use this code to start a discussion around this sort of change.

                             

                            https://github.com/stevemac007/hibernate-core/commit/7cdb4c829f28c5b029a3d43f50a54d4c89fc9665

                             

                            I'm not sure if this forum or somewhere else is the best for this review, please let me know where (if anywhere) is more appropriate.

                            • 11. Re: adding more information to revision entity
                              Adam Warski Master

                              Did you build Hibernate with the provided Gradle script? If so, you shuld get the correct logger classes.

                              I'm using Intellij, so I won't help much with Eclipse

                               

                              Adam

                              • 12. Re: adding more information to revision entity
                                Vyacheslav Sakhno Newbie

                                I actually need that functionality too. Can you create hibernate-envers issue for that with this patch in hibernate jira? https://hibernate.onjira.com

                                • 13. Re: adding more information to revision entity
                                  Steve Mactaggart Newbie

                                  I did get it compiling, and have been succesfully using my own patched version built from the supplied github commit.

                                   

                                  How can we proceed from here?  I still don't feel this code is ready to be supplied as a patch via JIRA, does anyone have any comments on how they would like to see this change.

                                   

                                  My reasoning for this being not ready is that there is an API breaking change in CustomTrackingRevisionListener

                                   

                                  I could refactor this to a new listener, would that be suitable?

                                  • 14. Re: adding more information to revision entity
                                    Adam Warski Master

                                    It's fine to change the API at "big enough" versions. But what should be done here in fact is rather wrapping all these parameters into an object, so such problems would be avoided in the future (adding more fields to an object shouldn't be a problem).

                                     

                                    Maybe we could also then have one listener, but I'm not entirerly sure right now.

                                     

                                    Adam

                                    1 2 3 Previous Next