10 Replies Latest reply on Jul 18, 2011 7:56 AM by blabno

    Restoring an audited entity that was deleted

    scatudal

      I'm trying to restore an audited entity that was deleted and I get an EntityNotFoundException.

      The @Id field is a generated value that comes from a sequence. But obviously in this case, I want the entity to be restored with the PK it had before being deleted.

      Is this supported? How can I achieve this?

      Thanks for your help,

      Here is the stacktrace if it can help:

      10:41:42,500 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator_2] TwoPhaseCoordinator.beforeCompletion - failed for com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple@4eedda
      javax.persistence.PersistenceException: javax.persistence.EntityNotFoundException: Unable to find ca.mcgill.muhc.scn.clinic.patient.Patient with id 1653
       at org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:516)
       at com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:114)
       at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:247)
       at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:86)
       at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:177)
       at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1389)
       at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:135)
       at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:87)
       at org.jboss.tm.usertx.client.ServerVMClientUserTransaction.commit(ServerVMClientUserTransaction.java:140)
       at org.jboss.seam.transaction.UTTransaction.commit(UTTransaction.java:52)
       at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:613)
       at org.jboss.seam.jsf.SeamPhaseListener.commitOrRollback(SeamPhaseListener.java:604)
       at org.jboss.seam.jsf.SeamPhaseListener.handleTransactionsAfterPhase(SeamPhaseListener.java:345)
       at org.jboss.seam.jsf.SeamPhaseListener.afterServletPhase(SeamPhaseListener.java:245)
       at org.jboss.seam.jsf.SeamPhaseListener.afterPhase(SeamPhaseListener.java:196)
       at com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:175)
       at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:114)
       at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
       at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
       at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:90)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:178)
       at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
       at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:390)
       at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:517)
       at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at ca.mcgill.muhc.scn.util.TimingFilter.doFilter(TimingFilter.java:35)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at com.brite.framework.ClassRedefinitionFilter.doFilter(ClassRedefinitionFilter.java:90)
       at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
       at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
       at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
       at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
       at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
       at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:182)
       at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
       at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
       at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
       at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
       at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
       at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
       at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
       at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
       at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
       at java.lang.Thread.run(Thread.java:619)
      Caused by: javax.persistence.EntityNotFoundException: Unable to find ca.mcgill.muhc.scn.clinic.patient.Patient with id 1653
       at org.hibernate.ejb.Ejb3Configuration$Ejb3EntityNotFoundDelegate.handleEntityNotFound(Ejb3Configuration.java:113)
       at org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:154)
       at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:143)
       at org.hibernate.envers.tools.Tools.getTargetFromProxy(Tools.java:77)
       at org.hibernate.envers.event.AuditEventListener.generateBidirectionalCollectionChangeWorkUnits(AuditEventListener.java:108)
       at org.hibernate.envers.event.AuditEventListener.onPostUpdate(AuditEventListener.java:166)
       at org.hibernate.action.EntityUpdateAction.postUpdate(EntityUpdateAction.java:200)
       at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:179)
       at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
       at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
       at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
       at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
       at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
       at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
       at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:366)
       at org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:504)
       ... 62 more
      


        • 1. Re: Restoring an audited entity that was deleted
          scatudal

          It is Hibernate that replaces the PK that I manually assigned with a generated one.

          I'm currently looking into the Hibernate Forums to see if it is possible to have PKs generated only when there is no value already assigned.

          • 2. Re: Restoring an audited entity that was deleted
            scatudal

            I still haven't found a decent solution to this or a solution that I like.

            I can remove the @GeneratedValue attribute on my @Id field, but I would then have to do manage id generation on my own. That might end up being the solution that displeases me the least.

            Do any of you have a better suggestion to make?

            Thanks

            • 3. Re: Restoring an audited entity that was deleted
              adamw

              Well, generally you can have either an @GeneratedValue id (maybe with a custom generator?), or assign the ids yourself.

              Adam

              • 4. Re: Restoring an audited entity that was deleted
                scatudal

                I extended the generator that we are using and only overwrote the generate method. It works like a charm so far.

                For those of you who are curious how I did this, here is what it looks like :

                package foo.bar;
                
                import java.beans.PropertyDescriptor;
                import java.io.Serializable;
                import java.lang.annotation.Annotation;
                
                import org.apache.commons.beanutils.PropertyUtils;
                import org.hibernate.HibernateException;
                import org.hibernate.engine.SessionImplementor;
                import org.hibernate.id.enhanced.SequenceStyleGenerator;
                
                public class MySequenceGenerator extends SequenceStyleGenerator {
                
                 private PropertyDescriptor property;
                
                 @Override
                 public Serializable generate( SessionImplementor session, Object obj )
                 throws HibernateException {
                
                 if( property == null ) {
                 property = findIdProperty( obj );
                 }
                
                 Long id;
                 try {
                 id = (Long)property.getReadMethod().invoke( obj );
                 } catch( Exception e ) {
                 throw new RuntimeException( String.format( "Could not invoke %s on %s class",
                 property.getName(), obj.getClass().getName() ), e );
                 }
                
                
                 return id == null || id == 0 ? super.generate( session, obj ) : id;
                 }
                
                 private PropertyDescriptor findIdProperty( Object obj ) {
                 for( PropertyDescriptor pd : PropertyUtils
                 .getPropertyDescriptors( obj.getClass() ) ) {
                
                 for( Annotation a : pd.getReadMethod().getAnnotations() ) {
                
                 if( a.annotationType().getName().equals( "javax.persistence.Id" ) ) {
                 return pd;
                 }
                 }
                 }
                 throw new RuntimeException( "no property tagged with @Id." );
                 }
                }


                • 5. Re: Restoring an audited entity that was deleted
                  scatudal

                  Is there a cleaner way to persist an entity that was loaded with the audit reader.

                  What I do now is the following :

                  MyEntity entity = loadEntityFromAudit();
                  
                  entityManager.merge( entity );
                  
                  // entityManager.contains( entity ) --> false
                  
                  entity = entityManager.find( MyEntity.class, entity.getId() );
                  
                  // entityManager.contains( entity ) --> true
                  
                  


                  The code above will give me a managed entity. I absolutely need it to be managed.

                  In the above example, had I used persist instead of merge, the following error would have been thrown :

                  org.hibernate.PersistentObjectException: detached entity passed to persist


                  Note that entityManager.persist() doesn't complain when it gets a detached entity that was created in a way similar to the following example :

                  MyEntity entity = new MyEntity();
                  entity.setData("asdfa");
                  
                  entityManager.persist(entity);
                  


                  No problem here, entity is managed.

                  Any suggestion?

                  Thanks all,

                  • 6. Re: Restoring an audited entity that was deleted
                    adamw

                    Try entity = entityManager.merge( entity ); :)

                    Adam

                    • 7. Re: Restoring an audited entity that was deleted
                      scatudal

                      Before telling you that it wouldn't work, I tried it again because it makes sense! And you know what, it does work! ;)

                      My bad! I probably didn't redeploy or recompile.

                      Thanks Adam

                      • 8. Re: Restoring an audited entity that was deleted
                        blabno

                        Gentleman,

                        How on earth should entityManager.merge(entity) not throw EntityNotFound exception if entity is retrived from auditReader (entity was removed previously)?

                        • 9. Re: Restoring an audited entity that was deleted
                          adamw

                          The entity is probably re-persisted with the same id.

                           

                          Adam

                          • 10. Re: Restoring an audited entity that was deleted
                            blabno

                            My previous thought (EntityNotFound) was caused by the fact that I tested Envers against already filled database, so there was no audit info for some entities.

                             

                            However I've tested merge with hibernate 3.6.5.Final and entity retrived from AuditReader gets persisted with new id!