8 Replies Latest reply on Mar 31, 2009 10:27 AM by anaholzbach

    Extending envers to audit viewed items (in addition to added

    anaholzbach

      Our application requires that we audit viewed items in addition to added, modified and deleted - we deal with protected health information (PHI) and so it's important to track who accessed the data. We have a seam application and a custom RevisionListener that records the user id that we get from the seam identity component, as suggested in the envers documentation.

      How much work would be involved in adding view audits to envers? I thought I'd create a PostLoadEventListener and a ViewWorkUnit, and add 'VIEW' to the enum RevisionType. The idea would be to have the rows viewed be recorded with a 3 for VIEW in the same vein as 0,1,2 for ADD, MOD, DEL. The onPostLoad method in the listener wouldn't have to be that different from the onPostInsert method in VersionsEventListener. Then I'd add a property for hibernate.ejb.event.post-load to my application's persitence.xml. Sounds pretty straightforward.

      I started by adding PostLoadEventListener to the list of interfaces that VersionsEventListener implements. The onPostLoad method did nothing special, just print a message for the moment. I ran into a NullPointerException on deployment (somewhere during listener validation?) in org.hibernate.property.BasicPropertyAccessor as hibernate tried to get a Getter for property 'originalId' in a null class. Also looking through org.hibernate.event.EventListeners and org.hibernate.ejb.EventListenerConfigurator it seems that PostLoadEventListener and PreLoadEventListener are set to DefaultPostLoadEventListener and DefaultPreLoadEventListener, respectively, as opposed to allowing them to be set by configuration, as is the case for the insert, delete and update. Any ideas?

      I tried this with both envers 1.1.0.GA + hibernate 3.3 and the new hibernate module hibernate-envers 3.5.0-SNAPSHOT, with the same result.

      Am I missing something or is this more complex than it seems?

      It would be nice to have this as a feature of hibernate-envers, eventually.

      Thanks,

      Ana

        • 1. Re: Extending envers to audit viewed items (in addition to a

          Hi Ana,

          I need such functionality to log read/visited entries, too.
          The only question is if it is a ENVERS task and not a task of the business logic you have to deliver?
          In my opinion ENVERS was created too support change management and nothing else.

          Regards
          Albert

          • 2. Re: Extending envers to audit viewed items (in addition to a
            anaholzbach

            Hi Albert,

            It *could* be implemented in our business logic. It just seems like it would be more seamless if done via envers, since that's what we use for the other operations.

            Cheers,

            Ana

            • 3. Re: Extending envers to audit viewed items (in addition to a
              adamw

              Hello,

              maybe you need to chain those two listeners, so you get both your listener and the default one. I don't remember if the default listeners are included by default (when the post-load/pre-load even listener property is set), but maybe not. So try separating them by a coma, e.g.:

              hibernate.ejb.event.post-load=org.hibernate.event.def.DefaultPostLoadEventListener,your.even.Listener
              

              apart from that, no other ideas, it should work. If you still get the exception, maybe post the stacktrace here.

              As for the sole load-logging, it may of course be done as you write. But maybe a better idea would be to have a separate "access_log" table, that would only store the id and the revision number, no data - as data is already stored in Envers tables. That way you don't store that much data on each access. I think you'll be able still to re-use a lot of Envers code to do that.

              Adam

              • 4. Re: Extending envers to audit viewed items (in addition to a
                anaholzbach

                Hi Adam,

                Thanks for your reply. Here's something else I found out: if I create my own listener not based on VersionsEventListener, I don't get the deployment error. But if I base it on VersionsEventListener, either by adding PostLoadEventListener to the list of interfaces it implements or by creating a listener that extends VersionsEventListener and implements PostLoadEventListener, even with chained listeners in any order, the stacktrace is the same as before (below).

                So, for the moment, I'll pursue an implementation that is independent of envers, though I'd like to stick to the data model as much as possible to keep taking advantage of the versions queries in envers and though I do hate to re-invent the wheel.

                Thanks again,

                Ana

                For what it's worth, here's the stacktrace:

                17:15:26,312 INFO [HbmBinder] Mapping class: org.partners.betr.emsi.model.EmsiSpecimen_versions -> EMSI_SPECIMEN_versions
                17:15:26,312 INFO [HbmBinder] Mapping class: org.partners.betr.emsi.model.Status_versions -> STATUS_versions
                17:15:26,328 WARN [ServiceController] Problem starting service persistence.units:jar=envers-lookup-test.jar,unitName=testDB
                javax.persistence.PersistenceException: [PersistenceUnit: testDB] Unable to build EntityManagerFactory
                 at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:677)
                 at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:132)
                 at org.jboss.ejb3.entity.PersistenceUnitDeployment.start(PersistenceUnitDeployment.java:246)
                 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.ejb3.ServiceDelegateWrapper.startService(ServiceDelegateWrapper.java:103)
                 at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:289)
                 at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:245)
                 at sun.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:86)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:978)
                 at $Proxy0.start(Unknown Source)
                 at org.jboss.system.ServiceController.start(ServiceController.java:417)
                 at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:86)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:210)
                 at $Proxy436.start(Unknown Source)
                 at org.jboss.ejb3.JmxKernelAbstraction.install(JmxKernelAbstraction.java:120)
                 at org.jboss.ejb3.Ejb3Deployment.startPersistenceUnits(Ejb3Deployment.java:627)
                 at org.jboss.ejb3.Ejb3Deployment.start(Ejb3Deployment.java:351)
                 at org.jboss.ejb3.Ejb3Module.startService(Ejb3Module.java:91)
                 at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:289)
                 at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:245)
                 at sun.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:86)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:978)
                 at $Proxy0.start(Unknown Source)
                 at org.jboss.system.ServiceController.start(ServiceController.java:417)
                 at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:86)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:210)
                 at $Proxy33.start(Unknown Source)
                 at org.jboss.ejb3.EJB3Deployer.start(EJB3Deployer.java:512)
                 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.interceptor.AbstractInterceptor.invoke(AbstractInterceptor.java:133)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:88)
                 at org.jboss.mx.interceptor.ModelMBeanOperationInterceptor.invoke(ModelMBeanOperationInterceptor.java:142)
                 at org.jboss.mx.interceptor.DynamicInterceptor.invoke(DynamicInterceptor.java:97)
                 at org.jboss.system.InterceptorServiceMBeanSupport.invokeNext(InterceptorServiceMBeanSupport.java:238)
                 at org.jboss.wsf.container.jboss42.DeployerInterceptor.start(DeployerInterceptor.java:87)
                 at org.jboss.deployment.SubDeployerInterceptorSupport$XMBeanInterceptor.start(SubDeployerInterceptorSupport.java:188)
                 at org.jboss.deployment.SubDeployerInterceptor.invoke(SubDeployerInterceptor.java:95)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:88)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:210)
                 at $Proxy34.start(Unknown Source)
                 at org.jboss.deployment.MainDeployer.start(MainDeployer.java:1025)
                 at org.jboss.deployment.MainDeployer.deploy(MainDeployer.java:819)
                 at org.jboss.deployment.MainDeployer.deploy(MainDeployer.java:782)
                 at sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:585)
                 at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:155)
                 at org.jboss.mx.server.Invocation.dispatch(Invocation.java:94)
                 at org.jboss.mx.interceptor.AbstractInterceptor.invoke(AbstractInterceptor.java:133)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:88)
                 at org.jboss.mx.interceptor.ModelMBeanOperationInterceptor.invoke(ModelMBeanOperationInterceptor.java:142)
                 at org.jboss.mx.server.Invocation.invoke(Invocation.java:88)
                 at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:264)
                 at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:659)
                 at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:210)
                 at $Proxy9.deploy(Unknown Source)
                 at org.jboss.deployment.scanner.URLDeploymentScanner.deploy(URLDeploymentScanner.java:421)
                 at org.jboss.deployment.scanner.URLDeploymentScanner.scan(URLDeploymentScanner.java:610)
                 at org.jboss.deployment.scanner.AbstractDeploymentScanner$ScannerThread.doScan(AbstractDeploymentScanner.java:263)
                 at org.jboss.deployment.scanner.AbstractDeploymentScanner$ScannerThread.loop(AbstractDeploymentScanner.java:274)
                 at org.jboss.deployment.scanner.AbstractDeploymentScanner$ScannerThread.run(AbstractDeploymentScanner.java:225)
                Caused by: org.hibernate.HibernateException: could not init listeners
                 at org.hibernate.event.EventListeners.initializeListeners(EventListeners.java:208)
                 at org.hibernate.cfg.Configuration.getInitializedEventListeners(Configuration.java:1338)
                 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1327)
                 at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:859)
                 at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:669)
                 ... 98 more
                Caused by: java.lang.NullPointerException
                 at org.hibernate.property.BasicPropertyAccessor.createGetter(BasicPropertyAccessor.java:312)
                 at org.hibernate.property.BasicPropertyAccessor.getGetter(BasicPropertyAccessor.java:299)
                 at org.hibernate.validator.event.ValidateEventListener.addSubElement(ValidateEventListener.java:113)
                 at org.hibernate.validator.event.ValidateEventListener.initialize(ValidateEventListener.java:94)
                 at org.hibernate.event.EventListeners$1.processListener(EventListeners.java:201)
                 at org.hibernate.event.EventListeners.processListeners(EventListeners.java:181)
                 at org.hibernate.event.EventListeners.initializeListeners(EventListeners.java:194)
                 ... 102 more


                • 5. Re: Extending envers to audit viewed items (in addition to a
                  adamw

                  Hello,

                  that's quite weird. I just tried adding PostLoadEventListener to AuditEvenListener with a method which just does a system.out.println, and it works. My configuration is:

                  <event type="post-load">
                   <listener class="org.hibernate.event.def.DefaultPostLoadEventListener" />
                   <listener class="org.hibernate.envers.event.AuditEventListener" />
                   </event>
                  


                  When I load an entity, the text is printed to the console. I'm using of course trunk Envers, but this is exactly the code that was released in 1.2.0.

                  Adam

                  • 6. Re: Extending envers to audit viewed items (in addition to a
                    anaholzbach

                    Hi Adam,

                    Yes it's weird. I'm using envers with JBoss, so this is what I tried:

                     <persistence-unit name="testDB">
                     <provider>org.hibernate.ejb.HibernatePersistence</provider>
                     <jta-data-source>java:enversTestDB</jta-data-source>
                     <properties>
                     <property name="hibernate.show_sql" value="true"/>
                     <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
                     <property name="hibernate.ejb.event.post-insert" value="org.jboss.envers.event.VersionsEventListener" />
                     <property name="hibernate.ejb.event.post-update" value="org.jboss.envers.event.VersionsEventListener" />
                     <property name="hibernate.ejb.event.post-delete" value="org.jboss.envers.event.VersionsEventListener" />
                     <property name="hibernate.ejb.event.post-load" value="org.hibernate.event.def.DefaultPostLoadEventListener, org.jboss.envers.event.VersionsEventListener" />
                     <property name="org.jboss.envers.revisionTypeFieldName" value="revision_type" />
                     <property name="org.jboss.envers.revisionFieldName" value="revision" />
                     <property name="jboss.entity.manager.factory.jndi.name" value="java:/emsiEntityManagerFactory"/>
                     </properties>
                     </persistence-unit>
                    


                    Thanks,

                    Ana

                    • 7. Re: Extending envers to audit viewed items (in addition to a
                      adamw

                      Hello,

                      did you have any luck? The error really looks weird :)

                      Adam

                      • 8. Re: Extending envers to audit viewed items (in addition to a
                        anaholzbach

                        Hi Adam,

                        Thanks for checking - after thinking about it I ended up choosing an implementation closer to what you suggested, i.e., not a new 'revision' for each time something is viewed, since viewing does not *really* create a revision, but a log in separate audit table that records the revision viewed, the name and id of the entity and the user who viewed it. Queries for viewed data will combine this table with the versions tables, which is okay.

                        My current listener is not based on VersionsEventListener so it doesn't throw an initialization error... though that continues to be bizarre.

                        Thanks!

                        Ana